Chris Payne Übersetzung: Michael Matzer
.NET Windows Forms ■ ■ ■
Oberf berfl rflächen hen ob objektorient entiert programmieren ADO.NET, ActiveX, Multithreading, Web Services Klassenbi nbiblio liothek des .NET Framework ken kenn ennenl enlernen
Markt + Technik Verlag
Bibliografische Information Der Deutschen Bibliothek Die Deutsche Bibliothek verzeichnet diese Publikation in der Deutschen Nationalbibliografie; detaillierte bibliografische Daten sind im Internet über abrufbar. Die Informationen in diesem Produkt werden ohne Rücksicht auf einen eventuellen Patentschutz veröffentlicht. Warennamen werden ohne Gewährleistung der freien Verwendbarkeit benutzt. Bei der Zusammenstellung von Texten und Abbildungen wurde mit größter Sorgfalt vorgegangen. Trotzdem können Fehler nicht vollständig ausgeschlossen werden. Verlag, Herausgeber und Autoren können für fehlerhafte Angaben und deren Folgen weder eine juristische Verantwortung noch irgendeine Haftung übernehmen. Für Verbesserungsvorschläge und Hinweise auf Fehler sind Verlag und Herausgeber dankbar. Authorisierte Übersetzung der amerikanischen Originalausgabe: „Teach Yourself .NET Windows Forms in 21 Days“ Alle Rechte vorbehalten, auch die der fotomechanischen Wiedergabe und der Speicherung in elektronischen Medien. Die gewerbliche Nutzung der in diesem Produkt gezeigten Modelle und Arbeiten ist nicht zulässig. Fast alle Hardware- und Software-Bezeichnungen, die in diesem Buch erwähnt werden, sind gleichzeitig auch eingetragene Marken oder sollten als solche betrachtet werden. Authorized translation from the English language edition, entiteled SAMS TEACH YOURSELF .NET Windows Forms in 21 DAYS by Chris Payne, published by Pearson Education, Inc. publishing as Sams Publishing, Copyright © 2002 All rights reserved. No part of this book may be reproduced or transmitted in any form or by any means, electronis or mechanical, including photocopying, recording or by any Information storage retrieval system, without permisson from Pearson Education, Inc. German language edition published by PEARSON EDUCATION DEUTSCHLAND, Copyright © 2002 Umwelthinweis: Dieses Buch wurde auf chlorfrei gebleichtem Papier gedruckt.
10 9 8 7 6 5 4 3 2 1 05 04 03
ISBN 3-8272-6455-3
© 2003 by Markt+Technik Verlag, ein Imprint der Pearson Education Deutschland GmbH, Martin-Kollar-Straße 10–12, D–81829 München/Germany Alle Rechte vorbehalten Übersetzung: Michael Matzer, Waldenbruch Lektorat: Rainer fuchs,
[email protected] Fachlektorat: Frank Langenau, Chemnitz Herstellung: Philipp Burkart,
[email protected] Satz: reemers publishing services gmbh, Krefeld, (www.reemers.de) Einbandgestaltung: Grafikdesign Heinz H. Rauner, Gmund Druck und Verarbeitung: Bercker, Kevelaer Printed in Germany
Inhaltsverzeichnis Einleitung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Wer dieses Buch lesen sollte . . . . . . . . . . . . . . . . . . . . . . . . . . . . Vorkenntnisse . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Software für die Beispiele. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Wie dieses Buch aufgebaut ist . . . . . . . . . . . . . . . . . . . . . . . . . . . Programmcode auf Begleit-CD und US-Website . . . . . . . . . . . . Konventionen. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Und zu guter Letzt... . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
17 17 18 18 19 20 21 21
Woche 1 – Vorschau . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
23
Tag 1
Mit Windows Forms beginnen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1.1 Einführung in die Windows-Programmierung . . . . . . . . . . . . . . Programmiersprachen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Win32-Programmierung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1.2 Was ist .NET?. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .NET-Kompilierung. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Die Common Language Runtime (CLR). . . . . . . . . . . . . . . . . . Das .NET Framework installieren. . . . . . . . . . . . . . . . . . . . . . . . Entwicklungsumgebungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1.3 Was ist .NET Windows Forms? . . . . . . . . . . . . . . . . . . . . . . . . . . Integration mit .NET . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Windows Forms-Steuerelemente. . . . . . . . . . . . . . . . . . . . . . . . . 1.4 Die objektorientierte Vorgehensweise . . . . . . . . . . . . . . . . . . . . . 1.5 So erstellen Sie Ihre erste Anwendung . . . . . . . . . . . . . . . . . . . . 1.6 Zusammenfassung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1.7 Fragen und Antworten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1.8 Workshop . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Quiz . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Übung. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
25 26 27 28 29 30 32 33 34 35 36 37 38 39 41 42 42 42 43
Tag 2
Windows Forms-Anwendungen erstellen . . . . . . . . . . . . . . . . . . . . . . . 2.1 Ein weiterer Blick auf Ihre Anwendung . . . . . . . . . . . . . . . . . . . Klassen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
45 46 47
5
Inhaltsverzeichnis
Namensräume und Assemblies . . . . . . . . . . . . . . . . . . . . . . . . . . Die Main-Methode . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Konstruktoren und Destruktoren. . . . . . . . . . . . . . . . . . . . . . . . . Ein abschließender Blick auf den Programmcode . . . . . . . . . . . Das Kompilieren von Windows Forms . . . . . . . . . . . . . . . . . . . . Eine vollständige Anwendung . . . . . . . . . . . . . . . . . . . . . . . . . . Zusammenfassung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Fragen und Antworten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Workshop . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Quiz . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Übung. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
49 54 58 62 63 67 74 74 75 75 75
Mit Windows Forms arbeiten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
77
2.2 2.3 2.4 2.5 2.6
Tag 3
3.1
Das objektorientierte Windows-Formular . . . . . . . . . . . . . . . . . . Mit dem Objekt Object arbeiten . . . . . . . . . . . . . . . . . . . . . . . . . Formular-Eigenschaften . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Formular-Methoden . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Ereignisbehandlung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Die Nachrichtenschleife . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Form-Ereignisse . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Zusammenfassung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Fragen und Antworten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Workshop . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Quiz . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Übungen. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
78 79 82 92 94 94 97 106 107 108 108 109
Menüs und Symbolleisten. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
111
4.1 4.2
112 114 120 122 125 126 133 136 146 147 148 148 148
3.2
3.3 3.4 3.5
Tag 4
4.3
4.4 4.5 4.6
6
Einführung in Windows Forms-Steuerelemente . . . . . . . . . . . . Menüs hinzufügen. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Menüs anpassen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Kontextmenüs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Symbolleisten, Statusleisten und Bildlaufleisten hinzufügen. . . Symbolleisten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Statusleisten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Bildlaufleisten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Zusammenfassung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Fragen und Antworten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Workshop . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Quiz . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Übung. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Inhaltsverzeichnis
Tag 5
Ereignisse in Windows Forms . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5.1 Was sind Ereignishandler? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Ereignisse behandeln . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Mehrfache Ereignisse behandeln . . . . . . . . . . . . . . . . . . . . . . . . 5.2 Delegaten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5.3 Ereignisse und Objekte . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Ereignishandler . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Benutzerdefinierte Ereignisse erstellen . . . . . . . . . . . . . . . . . . . . Steuerelemente sezieren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Benutzerdefinierte Delegaten erstellen . . . . . . . . . . . . . . . . . . . . 5.4 Zusammenfassung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5.5 Fragen und Antworten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5.6 Workshop . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Quiz . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Übung. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
151 152 153 156 158 162 162 163 168 169 173 173 174 174 175
Tag 6
Windows Forms mit Steuerelementen erweitern . . . . . . . . . . . . . . . . . 6.1 Das Steuerelement Button. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6.2 Die Steuerelemente CheckBox und RadioButton . . . . . . . . . . . 6.3 Das Steuerelement ComboBox . . . . . . . . . . . . . . . . . . . . . . . . . . Das Kombinationsfeld instantiieren . . . . . . . . . . . . . . . . . . . . . . Die Anzeige des Steuerelements steuern . . . . . . . . . . . . . . . . . . Die ComboBox verwenden . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6.4 Das Steuerelement DateTimePicker. . . . . . . . . . . . . . . . . . . . . . 6.5 Das Steuerelement ImageList . . . . . . . . . . . . . . . . . . . . . . . . . . . 6.6 Das Steuerelement ListBox . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6.7 Das Steuerelement PictureBox . . . . . . . . . . . . . . . . . . . . . . . . . . 6.8 Das Steuerelement TabControl . . . . . . . . . . . . . . . . . . . . . . . . . . 6.9 Die Steuerelemente TextBox und RichTextBox . . . . . . . . . . . . . Rich Text . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6.10 Das Steuerelement Timer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6.11 Das Steuerelement TreeView . . . . . . . . . . . . . . . . . . . . . . . . . . . 6.12 Zusätzliche nützliche Steuerelemente . . . . . . . . . . . . . . . . . . . . 6.13 Das Layout steuern. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6.14 Zusammenfassung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6.15 Fragen und Antworten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6.16 Workshop . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Quiz . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Übung. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
177 179 180 183 184 186 187 189 193 195 199 200 206 208 215 218 221 222 228 229 230 230 230
7
Inhaltsverzeichnis
Tag 7
Mit Dialogfeldern arbeiten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7.1 Informationen holen und anzeigen . . . . . . . . . . . . . . . . . . . . . . . 7.2 Unterschiedliche Arten von Dialogen. . . . . . . . . . . . . . . . . . . . . Modal vs. nichtmodal . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Die Standarddialogfelder . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7.3 Dialogfelder . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Eigene Dialogfelder erstellen . . . . . . . . . . . . . . . . . . . . . . . . . . . Informationen abrufen. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7.4 Standarddialogfelder . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Ein- und Ausgabe von Dateien . . . . . . . . . . . . . . . . . . . . . . . . . . Farben und Schriftarten wählen . . . . . . . . . . . . . . . . . . . . . . . . . Drucken . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7.5 Zusammenfassung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7.6 Fragen und Antworten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7.7 Workshop . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Quiz . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Übung. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
231 232 234 234 235 235 238 244 248 248 250 253 257 258 259 259 260
Woche 1 – Rückblick . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
261
Projekt 1: Eine Textverarbeitung erstellen. . . . . . . . . . . . . . . . . . Über das Textverarbeitungsprojekt. . . . . . . . . . . . . . . . . . . . . . . . Was Sie benötigen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Die Benutzeroberfläche aufbauen. . . . . . . . . . . . . . . . . . . . . . . . Einige nützliche Dinge mit Pfiff hinzufügen . . . . . . . . . . . . . . .
261 261 262 262 274
Woche 2 – Vorschau . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
279
Tag 8
Datenbindung in Windows Forms . . . . . . . . . . . . . . . . . . . . . . . . . . . .
281
8.1
282 283 284 285 287 290 290 293 296 306 307
8.2
8.3
8.4 8.5
8
Einführung in die Datenbindung . . . . . . . . . . . . . . . . . . . . . . . . Daten aus jeder beliebigen Quelle . . . . . . . . . . . . . . . . . . . . . . . Einfache Datenbindung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Eine Schaltfläche mit Daten verbinden . . . . . . . . . . . . . . . . . . . Das Bearbeiten gebundener Daten . . . . . . . . . . . . . . . . . . . . . . . Formulare mit Daten verbinden . . . . . . . . . . . . . . . . . . . . . . . . . Das Steuerelement DataGrid . . . . . . . . . . . . . . . . . . . . . . . . . . . Ein kurzer Blick auf ADO.NET . . . . . . . . . . . . . . . . . . . . . . . . . Die Interaktion mit einem DataGrid . . . . . . . . . . . . . . . . . . . . . Zusammenfassung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Fragen und Antworten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Inhaltsverzeichnis
8.6
Tag 9
Workshop . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Quiz . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Übung. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
307 307 308
ADO.NET einsetzen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
309
9.1
Was ist ADO.NET? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ADO.NET gegenüber ADO . . . . . . . . . . . . . . . . . . . . . . . . . . . . ADO.NET und XML. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Eine Datenbank anlegen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Eine Access-Datenbank . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Eine Datenbank in SQL Server 2000 anlegen . . . . . . . . . . . . . . SQL für die Datenbankabfrage . . . . . . . . . . . . . . . . . . . . . . . . . . Das SELECT-Statement . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Das INSERT-Statement. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Das UPDATE-Statement. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Das DELETE-Statement. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Einführung in DataSet . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Das Objektmodell von ADO.NET . . . . . . . . . . . . . . . . . . . . . . . Verbindung zur Datenbank aufnehmen und Daten abrufen. . . Daten bearbeiten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Andere ADO.NET-Objekte . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Parametrisierte Abfragen und gespeicherte Prozeduren . . . . . . . Das Objekt DataView . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . XML im Zusammenhang mit ADO.NET . . . . . . . . . . . . . . . . . Zusammenfassung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Fragen und Antworten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Workshop . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Quiz . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Übung. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
310 311 313 314 314 317 319 319 322 322 323 323 325 326 329 334 338 341 342 345 346 347 347 348
MDI-Anwendungen erstellen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10.1 Einführung in die Mehrfachdokumentschnittstelle . . . . . . . . . . MDI-Menüs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10.2 Eine MDI-Anwendung erstellen . . . . . . . . . . . . . . . . . . . . . . . . . Verschiedene Arten von untergeordneten Fenstern erstellen. . . 10.3 Die Besonderheiten von MDI-Formularen steuern . . . . . . . . . . Menüs. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Untergeordnete Fenster . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Auf untergeordnete Steuerelemente zugreifen . . . . . . . . . . . . . . 10.4 Zusammenfassung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
349 350 352 353 361 362 362 365 367 372
9.2
9.3
9.4 9.5
9.6
9.7 9.8 9.9 9.10
Tag 10
9
Inhaltsverzeichnis
10.5 10.6
Tag 11
Arbeiten mit der Windows-Ein-/Ausgabe . . . . . . . . . . . . . . . . . . . . . . .
377 378 380 388 393 396 400 402 405 406 411 412 413 413 413
Einführung in Streams. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Dateien und Verzeichnisse. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Dateien lesen und schreiben . . . . . . . . . . . . . . . . . . . . . . . . . . . . Binärdaten lesen und schreiben . . . . . . . . . . . . . . . . . . . . . . . . . Serialisierung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Das Steuerelement RichTextBox . . . . . . . . . . . . . . . . . . . . . . . . Das Objekt FileSystemWatcher. . . . . . . . . . . . . . . . . . . . . . . . . . Asynchrone Ein-/Ausgabe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Drucken unter .NET . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Zusammenfassung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Fragen und Antworten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Workshop . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Quiz . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Übung. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Formulare Internet-fähig machen . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
415
12.1
416 418 419 419 427 428 430
12.2
12.3
12.4
12.5 12.6 12.7
10
373 374 374 375
11.1 11.2
11.3 11.4 11.5 11.6 11.7 11.8
Tag 12
Fragen und Antworten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Workshop . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Quiz . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Übung. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Internetprotokolle. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Austauschbare Protokolle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Clients und Server . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Anforderungen mit WebRequest senden . . . . . . . . . . . . . . . . . . Anfragen mit WebResponse verarbeiten . . . . . . . . . . . . . . . . . . . Protokollspezifische Klassen . . . . . . . . . . . . . . . . . . . . . . . . . . . . Arbeiten mit dem Internet Explorer . . . . . . . . . . . . . . . . . . . . . . Windows Forms-Steuerelemente in den Internet Explorer einbetten . . . . . . . . . . . . . . . . . . . . . . . . . Internetsicherheit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Authentifizierung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Berechtigungsklassen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Zusammenfassung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Fragen und Antworten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Workshop . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Quiz . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Übung. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
430 433 433 434 435 436 436 436 437
Inhaltsverzeichnis
Tag 13
Grafische Anwendungen mit GDI+ . . . . . . . . . . . . . . . . . . . . . . . . . . . 13.1 Einführung in die Grafik und Bildbearbeitung unter Windows Pinsel und Stifte . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Rechtecke und Bereiche . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13.2 Das Ereignis Paint . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13.3 Text zeichnen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13.4 Grafiken anzeigen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13.5 Figuren zeichnen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Figuren ausfüllen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Figuren dauerhaft machen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Figuren umwandeln. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13.6 Windows Forms beschneiden . . . . . . . . . . . . . . . . . . . . . . . . . . . 13.7 Zusammenfassung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13.8 Fragen und Antworten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13.9 Workshop . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Quiz . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Übung. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
439 440 442 442 443 446 450 454 460 468 470 471 473 474 475 475 475
Tag 14
ActiveX . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14.1 Einführung in ActiveX. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14.2 ActiveX-Steuerelemente. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Das ActiveX-Steuerelement Webbrowser . . . . . . . . . . . . . . . . . . Die Typbibliotheken von Microsoft Office . . . . . . . . . . . . . . . . . 14.3 .NET-ActiveX-Steuerelemente erstellen . . . . . . . . . . . . . . . . . . . 14.4 Richtlinien für das Arbeiten mit ActiveX. . . . . . . . . . . . . . . . . . . 14.5 Zusammenfassung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14.6 Fragen und Antworten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14.7 Workshop . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Quiz . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Übung. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
477 478 480 482 485 499 500 502 502 503 503 504
Woche 2 – Rückblick . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
505
Projekt 2: Die Textverarbeitung erweitern. . . . . . . . . . . . . . . . . . MDI hinzufügen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . E/A hinzufügen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Druckfunktionen hinzufügen . . . . . . . . . . . . . . . . . . . . . . . . . . .
505 505 507 511
11
Inhaltsverzeichnis
Woche 3 – Vorschau . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
513
Tag 15
Webdienste . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
515
15.1
Was sind Webdienste? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Das Simple Object Access Protocol (SOAP). . . . . . . . . . . . . . . . Andere Protokolle für Webdienste. . . . . . . . . . . . . . . . . . . . . . . . Mit ASP.NET Webdienste erstellen. . . . . . . . . . . . . . . . . . . . . . . Webmethoden . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Die Dienstbeschreibung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Webdienste unter ASP.NET . . . . . . . . . . . . . . . . . . . . . . . . . . . . Webdienste in Anwendungen . . . . . . . . . . . . . . . . . . . . . . . . . . . Webdienste suchen. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Empfehlungen für den Einsatz von Webdiensten. . . . . . . . . . . . Zusammenfassung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Fragen und Antworten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Workshop . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Quiz . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Übung. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
516 519 520 520 523 524 526 526 534 535 536 537 538 538 538
Windows-Dienste . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
539
16.1 16.2 16.3
Was sind Windows-Dienste? . . . . . . . . . . . . . . . . . . . . . . . . . . . . Einen Windows-Dienst erzeugen . . . . . . . . . . . . . . . . . . . . . . . . Dienste zum Zusammenspiel mit Windows bringen . . . . . . . . . Windows-Dienste starten und beenden. . . . . . . . . . . . . . . . . . . . Ihre Dienste überwachen. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Schnittstellen für Windows-Dienste . . . . . . . . . . . . . . . . . . . . . . In Protokolldateien schreiben . . . . . . . . . . . . . . . . . . . . . . . . . . . Zusammenfassung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Fragen und Antworten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Workshop . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Quiz . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Übung. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
540 542 549 552 553 555 560 562 563 564 564 564
Fortgeschrittene Techniken für Steuerelemente in Windows Forms .
565
15.2
15.3 15.4 15.5 15.6 15.7 15.8
Tag 16
16.4 16.5 16.6 16.7 16.8
Tag 17
17.1 17.2
12
Noch einmal: Menüs und die Steuerelemente Listbox, ComboBox und TabControl . . . . . . . . . . . . . . . . . . . . . DrawMode festlegen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Die Steuerelemente ListBox und ComboBox erweitern . . . . . . Das Steuerelement TabControl erweitern . . . . . . . . . . . . . . . . .
566 568 570 578
Inhaltsverzeichnis
17.3 17.4 17.5 17.6
Menüs: Eine Frage des Besitzes . . . . . . . . . . . . . . . . . . . . . . . . . Zusammenfassung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Fragen und Antworten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Workshop . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Quiz . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Übung. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
581 586 587 588 588 588
Tag 18
Benutzerdefinierte Steuerelemente in Windows Forms . . . . . . . . . . . 18.1 Wozu benutzerdefinierte Steuerelemente erzeugen? . . . . . . . . . 18.2 Benutzerdefinierte Steuerelemente . . . . . . . . . . . . . . . . . . . . . . . Rekapitulation der Architektur für Steuerelemente . . . . . . . . . . Eigenschaften, Methoden und Ereignisse. . . . . . . . . . . . . . . . . . Ihr Steuerelement zeichnen . . . . . . . . . . . . . . . . . . . . . . . . . . . . Alles zusammensetzen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18.3 Benutzerdefinierte Steuerelemente in Windows Forms . . . . . . . 18.4 Benutzerdefinierte Steuerelemente aus vorhandenen erzeugen Vorhandene Steuerelemente erweitern. . . . . . . . . . . . . . . . . . . . Benutzersteuerelemente . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18.5 Zusammenfassung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18.6 Fragen und Antworten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18.7 Workshop . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Quiz . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Übung. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
589 590 591 592 594 599 605 609 610 611 613 617 618 618 619 619
Tag 19
Multithreading-Anwendungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19.1 Einführung in Multithreading. . . . . . . . . . . . . . . . . . . . . . . . . . . Probleme mit Threading . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Threading-Objekte verwenden . . . . . . . . . . . . . . . . . . . . . . . . . . 19.2 Eine Multithreading-Anwendung erstellen . . . . . . . . . . . . . . . . . Threads miteinander synchronisieren . . . . . . . . . . . . . . . . . . . . . Threadpooling einsetzen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19.3 Zusammenfassung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19.4 Fragen und Antworten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19.5 Workshop . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Quiz . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Übung. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
621 622 626 627 628 635 645 652 653 654 654 654
Tag 20
Windows Forms konfigurieren und bereitstellen. . . . . . . . . . . . . . . . . 20.1 Einführung in die .NET-Konfiguration. . . . . . . . . . . . . . . . . . . . 20.2 So konfigurieren Sie Ihre Anwendungen . . . . . . . . . . . . . . . . . .
655 656 659
13
Inhaltsverzeichnis
Konfigurationsabschnitte . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Der Abschnitt . . . . . . . . . . . . . . . . . . . . . . . . . Der Abschnitt . . . . . . . . . . . . . . . . . . . . . . . . . . . . Der Abschnitt <startup> . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Der Abschnitt <system.windows.forms> . . . . . . . . . . . . . . . . . . . Konfigurationswerte abrufen . . . . . . . . . . . . . . . . . . . . . . . . . . . . Lokalisieren Sie Ihre Anwendung . . . . . . . . . . . . . . . . . . . . . . . . Eingabehilfen. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Der einfache Teil der Arbeit: Bereitstellung . . . . . . . . . . . . . . . . Zusammenfassung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Fragen und Antworten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Workshop . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Quiz . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Übungen. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
660 662 664 665 666 667 669 676 677 678 679 680 680 680
Debugging und Profiling. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21.1 Debuggen in Windows Forms . . . . . . . . . . . . . . . . . . . . . . . . . . . 21.2 JIT-Debugging . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Das Werkzeug DbgClr. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Das Werkzeug CorDbg . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21.3 Profiling Ihrer Anwendung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21.4 Zusammenfassung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21.5 Fragen und Antworten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21.6 Workshop . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Quiz . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Übung. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
681 682 689 690 694 696 702 703 704 704 704
Woche 3 – Rückblick . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
705
Projekt 3: Das Textprogramm vervollständigen . . . . . . . . . . . . . . Eine Klasse für eine neue Seite erzeugen . . . . . . . . . . . . . . . . . . Der weitere Weg . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
705 705 722
Anhang A. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . A.1 Antworten auf die Quizfragen und Übungen . . . . . . . . . . . . . . .
723 724
Stichwortverzeichnis. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
809
20.3 20.4 20.5 20.6 20.7 20.8
Tag 21
14
Inhaltsverzeichnis
Inhaltsverzeichnis der Begleit-CD Bonuskapitel 1: Steuerelemente in Windows Forms. . . . . . . . . . . . . . 1.1 Die Klasse Control . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1.2 Die Steuerelemente Button, CheckBox und RadioButton. . . . . Das Steuerelement Button. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Das Steuerelement CheckBox. . . . . . . . . . . . . . . . . . . . . . . . . . . Das Steuerelement RadioButton . . . . . . . . . . . . . . . . . . . . . . . . . 1.3 Die Steuerelemente ComboBox, ListBox und CheckedListBox Das Steuerelement ComboBox . . . . . . . . . . . . . . . . . . . . . . . . . . Das Steuerelement ListBox . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Das Steuerelement CheckedListBox. . . . . . . . . . . . . . . . . . . . . . 1.4 Standarddialogfeld-Steuerelemente. . . . . . . . . . . . . . . . . . . . . . . Das Steuerelement ColorDialog . . . . . . . . . . . . . . . . . . . . . . . . . Dateidialogfelder . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1.5 Menüs. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ContextMenu . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . MainMenu . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . MenuItem . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1.6 Das Steuerelement DataGrid . . . . . . . . . . . . . . . . . . . . . . . . . . . 1.7 DateTimePicker . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1.8 Bildlauffähige Steuerelemente . . . . . . . . . . . . . . . . . . . . . . . . . . DomainUpDown . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Form . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . NumericUpDown . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Panel. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . PrintPreviewDialog . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . PropertyGrid. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . TabPage . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1.9 ErrorProvider . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1.10 GroupBox . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1.11 Bildlaufleisten. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . HScrollBar . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . VScrollBar . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1.12 ImageList . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1.13 Label-Steuerelemente . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1.14 ListView . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1.15 PictureBox . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1 2 11 11 12 13 13 14 16 18 19 20 20 25 25 26 26 28 32 33 35 36 39 39 40 40 42 42 43 44 45 45 45 46 47 50
15
Inhaltsverzeichnis
1.16 1.17 1.18 1.19
PrintDocument . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . PrintPreviewControl . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ProgressBar . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Textfeld-Steuerelemente . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . RichTextBox. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . TextBox . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Steuerelemente der Statusleiste. . . . . . . . . . . . . . . . . . . . . . . . . . TabControl . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Timer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Steuerelemente der Symbolleiste . . . . . . . . . . . . . . . . . . . . . . . . ToolTip . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . TrackBar . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . TreeView-Steuerelemente . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
50 51 52 53 55 57 58 59 61 61 63 64 65
Bonuskapitel 2: Steuerelemente in ADO.NET: Eigenschaften und Methoden . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2.1 Die DataSet- und verwandte Klassen . . . . . . . . . . . . . . . . . . . . . Constraint und ConstraintCollection . . . . . . . . . . . . . . . . . . . . . DataColumn und DataColumnCollection. . . . . . . . . . . . . . . . . 2.2 DataRelation und DataRelationCollection . . . . . . . . . . . . . . . . 2.3 DataRow und DataRowCollection . . . . . . . . . . . . . . . . . . . . . . . DataSet . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . DataTable und DataTableCollection . . . . . . . . . . . . . . . . . . . . . DataView . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2.4 Verwaltete Provider . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . OleDbCommand. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . OleDBCommandBuilder . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . OleDbConnection . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . OleDbDataAdapter . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . OleDbdataReader . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . OleDbError und OleDbErrorCollection . . . . . . . . . . . . . . . . . . OleDbParameter und OleDbParameterCollection . . . . . . . . . . OleDbTransaction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
71 72 72 73 75 77 79 81 85 86 87 88 88 90 92 93 94 96
1.20 1.21 1.22 1.23 1.24 1.25 1.26
16
Einleitung Willkommen bei .NET Windows Forms in 21 Tagen! Dieses Buch wird Sie Schritt für Schritt von den Grundlagen in Microsoft .NET Windows Forms zu recht fortgeschrittenen Themen führen. Während der nächsten 21 Tage erwartet Sie ein sehr informatives und aufregendes Abenteuer. Die Windows Forms-Technologie ist Bestandteil der .NET-Initiative Microsofts. Sie erlaubt Ihnen auf sehr einfache Weise leistungsfähige Windows-Anwendungen zu erstellen, wobei Sie fast jede beliebige Programmiersprache verwenden können, die Sie kennen. Vom einfachen Taschenrechner bis hin zu Datenbank-gestützten multithreaded Webdiensten können Sie praktisch alles erstellen (wenn Sie von Multithreading oder Datenbankzugriffen keine Ahnung haben, dann lesen Sie am besten weiter). Einfach ausgedrückt: Wenn Sie künftig Windows-Anwendungen entwickeln wollen, werden Sie Windows Forms benutzen. Während Sie sich durch die Lektionen dieses Buches arbeiten, werden Sie entdecken, auf welch einfache Art und Weise sich diese Aufgaben bewältigen lassen. Sie werden nicht nur Beispiele sehen und lernen, wie Sie diese und andere Anwendungen erzeugen, sondern werden auch ein Verständnis dafür entwickeln, das Sie in die Lage versetzt, bei jedem künftigen Windows-Programmierprojekt, das Sie vielleicht anpacken wollen, zuversichtlich an dieses Unterfangen heranzugehen.
Wer dieses Buch lesen sollte Es ist der Zweck dieses Buches, Einsteiger in Sachen Windows Forms mit dieser leistungsfähigen Technologie vertraut zu machen. »Einsteiger« ist jedoch ein recht allgemeiner Begriff. Ganz konkret sollten Sie dieses Buch verwenden, wenn Sie lernen wollen, wie Sie Windows-Anwendungen in .NET erstellen können, ganz gleich, ob Sie bereits Erfahrung mit ihrer Erstellung in einer anderen Umgebung haben oder nicht. Da aber Microsofts Zukunft auf .NET fußt, wird jeder Windows-Anwendungsentwickler früher oder später auf Windows Forms umsteigen müssen. Selbst wenn Sie Windows Forms bereits kennen, wird Ihnen dieses Buch ein unschätzbares Hilfsmittel sein, das dazu beiträgt, vergessenes Grundlagenwissen aufzufrischen und Sie auf fortgeschrittenere Projekte vorzubereiten. Wer bereits mit anderen Methoden der Anwendungsentwicklung für Windows vertraut ist, wird Windows Forms als Erleichterung empfinden, da es bislang lästige Arbeitsschritte wie
Einleitung
etwa Speicherverwaltung zu einem Kinderspiel macht. Sie werden sehen, dass viele der Kenntnisse, die Sie bereits erworben haben, sich auch weiterhin in diesem neuen Rahmenwerk anwenden lassen. Und schließlich: Wenn Sie einfach nur neugierig auf das .NET Framework sind, dient Ihnen dieses Buch als ausgezeichnete Einführung und führt Sie durch viele der zentralen Themen, die sich auf die Anwendungsentwicklung beziehen.
Vorkenntnisse Die einzigen absolut erforderlichen Voraussetzungen sind Grundkenntnisse im Umgang mit dem Betriebssystem Windows, von der Bedienung der Eingabeaufforderung bis zum Arbeiten mit Anwendungsfenstern. Die Kenntnis einer .NET-Programmiersprache wie C# oder VB .NET wird sich als äußerst hilfreich erweisen, ist aber keine Voraussetzung. Dieses Buch führt Sie rasch in die Grundlagen dieser beiden Programmiersprachen ein und stellt jedes neue Thema vor, bevor diese Sprachen verwendet werden. Wenn Sie dem Code und Text folgen können – und sowohl C# als auch VB .NET sind einfach zu lesen – dann werden Sie keine Probleme haben. Kenntnisse des .NET Frameworks sind nicht erforderlich. Dieses Buch befasst sich gründlich mit dem Framework und wird nötigenfalls auf weitere Ressourcen verweisen.
Software für die Beispiele Die einzige Software, die Sie neben Windows NT 4.0 mit Service Pack (SP) 6, Windows 2000 oder Windows XP benötigen, ist das .NET Framework und der Windows-Editor (NotePad). Das .NET Framework ist notwendig, um Windows Forms-Anwendungen erstellen zu können, und lässt sich kostenlos von Microsofts Website http://www.microsoft.com/net herunterladen. Außerdem finden Sie das .NET Framework SDK auf der Begleit-CD. Um Anwendungen zu schreiben, brauchen Sie lediglich ein Textprogramm, das einfache Textdateien (zum Beispiel mit der Endung .txt) erstellen kann. Der Windows-Editor eignet sich für diesen Zweck hervorragend. Wenn Ihnen ein anderes Textprogramm wie etwa Word, WordPad oder gar FrontPage eher behagt, dann können Sie selbstverständlich auch dieses benutzen. Visual Studio .NET wird hier nicht benötigt. In der Tat wird es im ganzen Buch recht selten verwendet. Während es zutrifft, dass Visual Studio .NET eine Menge ausgezeichneter Funktionen für die Anwendungsentwicklung bereitstellt, sind wir doch der Meinung, dass
18
Wie dieses Buch aufgebaut ist
es am besten ist, die Aufgaben »zu Fuß« zu bewältigen, bevor man sie von einer solchen Entwicklungsumgebung erledigen lässt. Auf lange Sicht werden Sie dafür dankbar sein. Um schließlich die Datenbank-bezogenen Beispiele verwenden zu können, benötigen Sie irgendeine Form von Datenbanksystem, das zu OLE DB konform ist, beispielsweise Microsoft Access oder SQL Server. Ohne diese Werkzeuge werden Sie die diesbezüglichen Beispiele nicht nutzen können.
Wie dieses Buch aufgebaut ist Das Buch ist als Serie von Lernabschnitten, als Tutorial, aufgebaut, so dass jeder Tag auf dem vorhergehenden aufbaut. Es ist sinnvoll, Schritt für Schritt vorzugehen, wenn auch nicht zwingend. Nach der Erörterung des Themas folgen jeweils ein paar Beispiele, die von Analysen begleitet werden. Jedes Kapitel endet mit Antworten auf häufig gestellte Fragen, einem kurzen Quiz und einer Übung. Diese Bestandteile sind sorgfältig ausgewählt, so dass sie Ihr Wissen erweitern und Ihre Erfahrung mit der Technologie vertiefen können. Das Buch ist in drei logische Abschnitte aufgeteilt, von denen sich jeder über eine Woche erstreckt, sowie in einen Block aus drei Anhängen: 쐽
Die erste Woche werden Sie damit verbringen, die Grundlagen von .NET und Windows Forms zu erlernen. Sie werden verstehen, was zur Erstellung einer Windows Forms-Anwendung gehört, werden herausfinden, wie man dabei vorgeht und mit den Steuerelementen umgehen lernen, die Ihre Anwendung mit Funktionen versehen. Die feste Grundlage, die Sie in dieser Woche aufbauen, hilft Ihnen in den nächsten zwei Wochen.
쐽
Während der gesamten zweiten Woche befassen Sie sich mit Technologien, die den Funktionsumfang Ihrer Windows Forms-Anwendung erweitern, angefangen mit ActiveX bis hin zu ADO.NET. Sie sind nicht unbedingt ein Teil von Windows Forms, aber Sie werden sie höchstwahrscheinlich bei der Anwendungsentwicklung benötigen.
쐽
In der dritten Woche reichen die Windows Forms-Themen schon ein gutes Stück tiefer. Sie lernen, wie Sie Ihre Anwendungen vollständig anpassen, angefangen vom Aufbau leistungsfähiger (und vermarktbarer) Steuerelemente bis hin zur Konfiguration von Installationsoptionen und Sprachen. Sie werden sich einige Techniken aneignen, mit denen Sie die Leistung Ihrer Anwendungen beträchtlich erhöhen können: von Multithreading bis hin zu Profiling.
19
Einleitung
Am Ende jeder Woche werden Sie ein wenig Zeit auf das Rekapitulieren der Lektionen verwenden und daraufhin das Wissen dieser Woche auf ein echtes Projekt anwenden. Sie werden ein Textverarbeitungsprogramm ähnlich wie Microsoft Word von Anfang an aufbauen, indem Sie es durch neu erlernte Funktionen und Technologien erweitern. Die Rekapitulation jeder Woche beruht auf den entsprechenden Lektionen. Diese Projekte bieten Ihnen die Gelegenheit, Ihre Kenntnisse, die Sie während dieser Woche erworben haben, anzuwenden und zu festigen. 쐽
Schließlich erhalten Sie mit den Anhängen eine vollständige Referenz für die in dem vorliegenden Buch behandelten Themen. Um Antworten auf die Quizfragen zu erhalten, können Sie hier nachschlagen. Damit können Sie auch Ihr Wissen über die im Buch vorgestellten Steuerelemente und Objekte auffrischen.
Programmcode auf Begleit-CD und US-Website Auf der Begleit-CD finden Sie den vollständigen Code für alle Beispiele des Buches in der amerikanischen Originalversion. Die im Buch wiedergegebenen Listings unterscheiden sich hiervon nur darin, dass Beschriftungen von sichtbaren Elementen der Benutzeroberfläche sowie die Kommentare ins Deutsche übersetzt wurden. Bei eventuellen Problemen mit der Lokalisierung Ihres eigenen Codes können Sie so durch Vergleich mit dem Originalcode die Fehlerursachen schnell einkreisen. Den Code für dieses Buch können Sie auch von der Sams Publishing-Website herunterladen: http://www.samspublishing.com/. Geben Sie die ISBN in das Suchfeld ein (0672323206 – ohne Bindestriche!) und klicken Sie auf SEARCH. Sobald der Buchtitel angezeigt wird, klicken Sie darauf und gehen auf eine Seite, wo Sie den Code herunterladen können. Auf dieser Website finden Sie gegebenenfalls auch aktualisierte bzw. fehlerbereinigte Versionen der Codebeispiele. Weitere Ressourcen finden Sie auf meiner Homepage http://www.clpayne.com. Hier finden Sie Links zu entsprechenden Informationen wie etwa Artikeln und Quellcode.
20
Konventionen
Konventionen Im Buch werden die folgenden typographischen Konventionen verwendet: 쐽
Codezeilen, Befehle, Anweisungen, Variablen und jeglicher Text, den Sie entweder eingeben oder der auf dem Bildschirm erscheint, werden in Schreibmaschinenschrift angezeigt. Benutzereingaben werden häufig in Fettschrift dargestellt.
쐽
Platzhalter in Syntaxbeschreibungen werden kursiv gedruckt. Ersetzen Sie den Platzhalter durch den tatsächlichen Dateinamen, Parameter oder das Element, das Sie verwenden möchten.
쐽
Manchmal sind Codezeilen zu lang, als dass sie auf eine einzelne Zeile auf der Seite passen. An der fehlenden Zeilennummer können Sie erkennen, wenn eine Codezeile noch zur darüber liegenden Zeile gehört. Die Analyseabschnitte nach Codelistings besprechen den Code im Detail. Hier finden Sie heraus, was ein bestimmtes Stück Code bewirkt und wie es funktioniert, sowie häufig eine Betrachtung Zeile für Zeile.
쐽
Ferner enthält dieses Buch Hinweise, Tipps und Warnungen, die Ihnen dabei helfen sollen, wichtige oder hilfreiche Informationen schneller auszumachen. Einige stellen nützliche Hilfestellungen zum effizienteren Arbeiten dar. Außerdem finden Sie nützliche Gegenüberstellungen der Art »Tun Sie dies/Tun Sie dies nicht«. Hier finden Sie Vorschläge über den richtigen Weg, eine Aufgabe zu lösen (und Warnungen vor dem falschen).
Und zu guter Letzt... Ich hoffe, Sie haben viel Spaß beim Erlernen dieser interessanten Technologie. Sie wird es Ihnen ermöglichen, mit Windows viele verblüffende Dinge, die auf dem heutigen Markt sehr gefragt sind, anzustellen. Als Leser dieses Buches sind Sie für Autor, Übersetzer, Fachlektor und Verlag der wichtigste Kritiker und Kommentator. Bitte teilen Sie uns mit, was Sie an diesem Buch positiv finden, was wir besser machen könnten oder was Ihnen sonst am Herzen liegt. Wenden Sie sich bitte an den Lektor Rainer Fuchs,
[email protected], der Ihnen weiterhilft oder Ihre Anfrage weiterleitet.
21
Tag 1
Mit Windows Forms beginnen
25
Tag 2
Windows Forms-Anwendungen erstellen
45
Tag 3
Mit Windows Forms arbeiten
77
Tag 4
Menüs und Symbolleisten
111
Tag 5
Ereignisse in Windows Forms
151
Tag 6
Windows Forms mit Steuerelementen erweitern
177
Tag 7
Mit Dialogfeldern arbeiten
231
Tag 8
Datenbindung in Windows Forms
281
Tag 9
ADO.NET einsetzen
309
Tag 10
MDI-Anwendungen erstellen
349
Tag 11
Arbeiten mit der Windows-Ein-/Ausgabe
377
Tag 12
Formulare Internet-fähig machen
415
Tag 13
Grafische Anwendungen mit GDI+
439
Tag 14
ActiveX
477
Tag 15
Webdienste
515
Tag 16
Windows-Dienste
539
Tag 17
Fortgeschrittene Techniken für Steuerelemente in Windows Forms
565
Benutzerdefinierte Steuerelemente in Windows Forms
589
Tag 19
Multithreading-Anwendungen
621
Tag 20
Windows Forms konfigurieren und bereitstellen
655
Tag 21
Debugging und Profiling
681
Tag 18
W O C H E
W O C H E
W O C H E
Woche 1: Die Grundlagen Auf Ihrer Reise durch Windows Forms erlernen Sie in der ersten Woche die Grundlagen. Die ersten sieben Lektionen vermitteln Ihnen ein festes Fundament, so dass Sie in den Wochen 2 und 3 fortgeschrittenere Themen angehen können. Tag 1 mit dem Titel »Mit Windows Forms beginnen« stellt Ihnen das .NET Framework, Windows Forms und die Windows-Programmierung vor. Sie werden in dieser Lektion auch Ihre erste Anwendung sehen. An Tag 2 mit dem Titel »Windows Forms-Anwendungen erstellen« werden Sie anfangen, Ihre erste eigene Windows-Anwendung zu erzeugen und mehr darüber erfahren, wie Sie diese Anwendung kompilieren und ausführen. Tag 3 mit dem Titel »Mit Windows Forms arbeiten« ist eine sehr wichtige Lektion. Sie erfahren alles über das Objekt Form, den Hauptbestandteil jeder Windows Forms-Anwendung. Sie werden erstmals auch einen Blick darauf werfen, wie Sie Ereignisse behandeln, d.h. wie Sie Ihre Anwendung auf Benutzereingaben reagieren lassen. An Tag 4 mit dem Titel »Menüs und Symbolleisten« werden Sie von vielen der Steuerelemente im Windows Forms-Framework erfahren, die Ihnen das Leben als Entwickler sehr erleichtern. Tag 5, »Ereignisse in Windows Forms«, lehrt Sie, wie Sie auf verschiedene Geschehnisse in Ihrer Anwendung, wie etwa Mausklicks auf Schaltflächen oder Menüs, reagieren können. An Tag 6, der den Titel »Windows Forms mit Steuerelementen erweitern« trägt, werden Sie Ihr Repertoire an Windows Forms-Steuerelementen beträchtlich aufbessern: Sie lernen alle wichtigen Steuerelemente kennen, die an Tag 4 nicht behandelt wurden. Tag 7, »Mit Dialogfeldern arbeiten«, bringt Ihnen schließlich alles über eine besondere Art von Windows Forms bei. Es gibt eine ganze Reihe von Dialogfeldtypen, die in der Windows-Programmierung sehr nützlich sind. Am Ende der Woche werden Sie das Gelernte rekapitulieren und es dazu verwenden, eine funktionierende Anwendung zu erzeugen. Lassen Sie uns ohne weiteren Verzug anfangen!
24
Mit Windows Forms beginnen
1
Mit Windows Forms beginnen
Willkommen zu Tag 1, Ihrem ersten Schritt in die Welt von Windows Forms! Windows Forms als Bestandteil von Microsoft .NET ist eine neue Technologie, die es Ihnen erlaubt, Windows-Anwendungen auf schnelle und einfache Weise zu erstellen. Heute fangen Sie damit an, worum es bei Windows Forms und .NET geht, und Sie werfen einen Blick auf echten Code. Heute 쐽
entdecken Sie, was Windows-Programmierung für Entwickler bedeutet,
쐽
erfahren Sie, was .NET und Windows Forms sind,
쐽
finden Sie etwas über die Vorteile heraus, die Ihnen .NET und Windows Forms bieten,
쐽
erkunden Sie, wie die Common Language Runtime und die Microsoft Intermediate Language funktionieren,
쐽
lernen Sie, wie Sie das .NET Framework installieren,
쐽
untersuchen Sie eine einfache Windows Forms-Anwendung.
1.1
Einführung in die Windows-Programmierung
Das Wichtigste zuerst: Was ist ein Windows-Programm? Vielleicht sollten wir noch einen weiteren Schritt zurück tun und fragen: Was ist Microsoft Windows? Da Sie nun mal auf dem Weg sind, ein Entwickler zu werden, müssen Sie Windows mit anderen Augen betrachten. Wenn Sie bereits ein Programmierer sind, dann haben Sie bereits gelernt, ein Betriebssystem zu steuern. Aus der Sichtweise eines Normalbenutzers ist Microsoft Windows ein Betriebssystem, das den Computer zum Laufen bringt. Es gestattet ihm Dokumente zu erstellen und zu speichern, Berechnungen auszuführen, im Internet zu surfen, Dateien auszudrucken und so weiter. Wenn Sie dieses Buch lesen, dann behagt Ihnen diese Beschreibung höchstwahrscheinlich nicht, denn Sie wollen mehr wissen: wie alles unter der Motorhaube funktioniert und wie Sie Windows dazu bringen können, bestimmte Dinge für Sie zu erledigen. Unter seinem nett anzusehenden Äußeren ist Microsoft Windows – genau wie jedes beliebige andere Betriebssystem – eine Umgebung für Übersetzer und zuweilen auch selbst ein Übersetzer. Windows und seine Anwendungen nehmen vom Benutzer eine Eingabe entgegen und übersetzen sie in etwas, das ein Computerprozessor verstehen kann – nämlich Einsen und Nullen – und umgekehrt. Abbildung 1.1 gibt dieses mehrschichtige Paradigma wieder. Windows selbst weiß nicht, wie es jede Eingabe übersetzen soll, und das ist der Punkt, an dem Windows-Anwendungen in Aktion treten.
26
Einführung in die Windows-Programmierung
Benutzereingabe
Microsoft Windows
CPU
0101101 1001..
Abbildung 1.1: Windows fungiert als Übersetzer zwischen dem Benutzer und dem Hauptprozessor.
Ein Windows-Programm wird von einem Entwickler geschrieben und sagt Windows, was es tun soll, um auf eine Eingabe zu reagieren. Diese Eingabe könnte in einem Tastendruck bestehen, einem Mausklick oder auch einfach in einem Programmstart. Durch Verwendung einer Programmiersprache – einer Sammlung von Schlüsselwörtern, Anweisungen, Begriffen und so weiter, die für den Computer von besonderer Bedeutung sind – schreibt ein Programmierer Befehle für das Betriebssystem, so dass es weiß, wie es Eingaben in Maschinensprache zu übersetzen hat. Was ist also ein Windows-Programm? Es ist ein Befehlssatz, der Windows mitteilt, was es tun und wie es auf eine Benutzereingabe reagieren soll. Als Entwickler werden Sie solche Instruktionen schreiben – vertrauen Sie mir, es ist einfacher, als Sie glauben. Wir werden diesen Vorgang näher unter die Lupe nehmen, wenn Sie Ihre ersten Anwendungen heute und morgen auf die Beine stellen.
Programmiersprachen Wie bereits erwähnt, ist eine Programmiersprache das, womit ein Programmierer das Betriebssystem instruiert. Sie enthält bestimmte Schlüsselbegriffe und eine Syntax. Auch wenn diese Elemente für den Laien wenig Sinn ergeben mögen, entsprechen sie einer wohldefinierten Logik. Selbst ohne Erfahrung auf diesem Gebiet haben Sie wahrscheinlich schon einmal von einigen Programmiersprachen gehört: C, C++, C# (»sih scharp« ausgesprochen), Visual Basic, Pascal, Java und so weiter. Warum so viele? Jede Sprache geht das Thema ein wenig anders an und stellt dem Entwickler andere Wahlmöglichkeiten bereit; ihre Anwendungen ähneln gesprochenen Sprachen sehr. Beispielsweise kennt ein Eskimo (Inuit) zehn Ausdrucksweisen für den Begriff »Schnee«, wohingegen die englische Sprache nur wenige besitzt. In ähnlicher Weise verfügt C++ über verschiedene Möglichkeiten, »Funktionszeiger« auszudrücken, während Visual Basic nur wenige oder keine besitzt. Das bedeutet nun nicht, dass eine der beiden Sprachen mangelhaft ist, sondern dass die zwei sich aus unterschiedlichen Wurzeln entwickelt haben. Darüber hinaus verwenden die meisten Sprachen (mit den Ausnahmen, die Sie im Abschnitt »Was ist .NET?« kennen lernen werden) unterschiedliche Paradigmen, um Windows-Anwendungen zu erstellen. C- und C++-Programme verwenden häufig die
27
Mit Windows Forms beginnen
Microsoft Foundation Classes (MFC), wohingegen Visual Basic seinen eigenen Formulardesigner besitzt. Das führte dazu, dass das Erlernen einer neuen Sprache häufig auch bedeutete, eine neue Methode zur Erstellung von Windows-Anwendungen zu erlernen (was inzwischen in .NET nicht mehr der Fall ist; mehr darüber später). Programmiersprachen werden in der Regel nicht vom Betriebssystem verstanden. Vielmehr übersetzt ein Zwischenprogramm namens Compiler die Programmiersprache in Maschinensprache, also in etwas, was das Betriebssystem versteht. Man könnte theoretisch ebenfalls in Maschinensprache schreiben, doch die meisten Menschen verstehen Binärbefehle nicht. Beispielsweise dürfte die Zeichenfolge »010101111« nach Unsinn aussehen, ergibt aber für den Computer tatsächlich einen Sinn. Die Sprachen, die Sie in diesem Buch benutzen werden, sind C# und Visual Basic .NET (VB .NET) – die erste, weil sie speziell für den Einsatz in der .NET-Umgebung entworfen wurde, und die zweite, weil die meisten Anfänger diese leicht erlernbare Sprache verwenden. Es ist meistens einfach, die eine in die jeweils andere zu übersetzen. Beispielsweise zeigt das folgende Stück Code zwei Befehlszeilen: eine in C# und die andere in Visual Basic .NET; beide tun das Gleiche. C# System.Console.WriteLine("Hello World!"); VB .NET System.Console.WriteLine("Hello World!")
In diesem Fall besteht der einzige Unterschied im Semikolon am Schluss der ersten Befehlszeile. Im ganzen Buch werden Ihnen Codebeispiele in beiden Sprachen angeboten, so dass Sie das bevorzugte Beispiel wählen können. Ich halte es für eine gute Idee, wenn Sie entweder C# oder VB .NET vor dem Lesen dieses Buches kennen. Sie bekommen zwar einiges dieser Sprachen durch das Lesen mit, aber Ihnen würden viele Elemente entgehen, die Sie sonst beim separaten Erlernen dieser Programmiersprachen erhalten würden.
Win32-Programmierung Man hört häufig den Begriff »Win32«, mit dem Entwickler um sich werfen. Win32Anwendungen sind einfach Anwendungen, die für eine 32-Bit-Version von Windows (also Windows 95, 98, ME, 2000 und XP) erstellt wurden. Ältere Versionen von Windows verwendeten 16 Bit und werden daher als Win16 bezeichnet. Der Übergang von 16 zu 32 Bit stellt Ihnen viel mehr Verarbeitungsleistung zur Verfügung und gestattet Ihnen mehr Kontrolle über den Computer. Win16-Programme laufen auf einem Win32-Betriebssystem, das gilt aber nicht umgekehrt wegen beschränkter Verarbeitungsleistung und fehlender Abwärtskompatibilität.
28
Was ist .NET?
Sehr einfach ausgedrückt sind also 32 Bit besser als 16 Bit, weil Sie damit mehr in der gleichen Zeit verarbeiten können. Für einen Computerprozessor heißt 32 Bit, dass er auf 32 Bit lange Daten im Speicher zugreifen und sie verarbeiten kann, was wesentlich mehr »Platz« bedeutet als 16 Bit. In der heutigen EDV-Welt sind die meisten Anwendungen, die Sie sehen, Win32 – genau wie alle Anwendungen, die Sie in diesem Buch erstellen werden. Win16-Programme bezeichnet man als »Legacy«- oder »Vermächtnis«-Programme, weil sie noch aus der Frühzeit der modernen Computerei stammen (also bis ca. 1995). Viele Entwickler halten nichts von Legacy-Anwendungen, weil diese Programme meist recht alt sind und ihnen die modernen Verarbeitungstechniken fehlen.
1.2
Was ist .NET?
.NET ist Microsofts Initiative, eine neue Generation von Windows-Anwendungen aus der Taufe zu heben. .NET vereinfacht viele der hinderlichen Aspekte in der Windows-Programmierung zu einer einfachen Architektur und integriert dabei das Internet eng in alle Anwendungen. Während .NET ein Konzept ist, bringt das .NET Framework es zum Laufen. Sie werden das Framework bei der Arbeit mit Windows Forms ausgiebig nutzen. Einfach ausgedrückt ist das .NET Framework eine Architektur, auf deren Grundlage sowohl Internet- als auch Desktop-basierte Anwendungen erstellt und ausgeführt werden können. Für die Arbeitsweise von Windows stellt es eine völlig neue Welt dar, indem es ein standardisiertes System bereitstellt, das alle Anwendungen nutzen können, anstelle der vielen unterschiedlichen Frameworks, die heute verfügbar sind. .NET stellt für diese Anwendungen auch eine neue Umgebung bereit, in der sie ablaufen können. Das ist effizienter, sicherer und leistungsfähiger. Kurz und gut: Mit Hilfe von .NET können Sie Anwendungen leichter als je zuvor erstellen. Eines der Hauptziele von .NET besteht darin, Anwendungen oder Anwendungskomponenten über das Internet als Dienste (Services) bereitzustellen. Ein Dienst ist eine Anwendung, die über das Web zur Verfügung steht und sich direkt oder von anderen Anwendungen nutzen lässt. Diese Dienste wären für jeden verfügbar, der sie braucht; man wählt sich einfach ins Web ein und stellt die Verbindung zum Dienst her. Stellen Sie sich vor, Sie hätten nicht solche Software wie Microsoft Word auf Ihrem Computer zu installieren. Statt dessen gehen Sie online und nutzen es von der Microsoft-Website aus, genauso, als befände es sich auf Ihrem Computer. Abbildung 1.2 illustriert dieses Konzept.
29
Mit Windows Forms beginnen
Ihr Computer
Befehle senden
Internet
gelieferte Daten senden
Dienst
Abbildung 1.2: Ein Webdienst ist eine Anwendung oder Komponente, die über das Internet zum Gebrauch bereitsteht.
Dieses Paradigma erspart sowohl Endanwendern als auch Entwicklern Kopfschmerzen. Die Endanwender müssen sich keine Sorgen machen, ob ihre Installation klappen wird oder ob sie ihre Anwendung pflegen, also aktualisieren müssen. Der Entwickler muss sich nicht sorgen wegen Installationsproblemen oder der Pflege mehrerer Programmversionen auf möglicherweise Tausenden von Anwendercomputern. Er erstellt das Programm einfach und stellt es über das Internet zur Verfügung, so dass Anwender vorbeischauen können und es einsetzen, wann sie es benötigen. Wenn der Entwickler eine neue Version erstellt, stellen die Benutzer eine Verbindung nur deshalb her, um die neuen Funktionen zu nutzen. In den nächsten Abschnitten erfahren Sie mehr über die Komponenten von .NET und wie sie zusammenwirken, um Ihre Arbeit als Entwickler zu vereinfachen. Es mag zwar zunächst etwas trocken zu lesen sein, doch Sie werden diese Grundlage brauchen, bevor Sie anfangen, eigene Anwendungen zu erstellen.
.NET-Kompilierung Eine nicht auf .NET basierende Anwendung funktioniert folgendermaßen: Sie wird von einem Entwickler in einer Programmiersprache geschrieben und dann zu einer ausführbaren Anwendung kompiliert (das ist ein sehr wichtiges Konzept). Das Betriebssystem stellt die Umgebung für die Ausführung der Anwendung bereit und bietet ihr je nach Bedarf Zugriff auf seine Ressourcen. In einem Multitasking-Betriebssystem wie Windows lassen sich mehrere Anwendungen gleichzeitig ausführen, denn jede Anwendung hat ihre eigenen Betriebssystemressourcen (Speicher usw.). Normalerweise respektiert jede Anwendung die Ressourcen der anderen, so dass keine Konflikte auftreten. Jede Anwendung hat sich auch selbst zu pflegen; das heißt sie wird ausgeführt und räumt sozusagen hinter sich wieder auf, wobei sie Ressourcen wieder freigibt, sobald sie nicht mehr gebraucht werden. Das ist im Allgemeinen eine gute Arbeitsweise. Anwendungen sind üblicherweise mit sich selbst beschäftigt und kümmern sich nicht darum, was andere Anwendungen so treiben. So könnte Anwendung A beispielsweise abstürzen, während Anwendung B weiterläuft, weil die beiden sich keine Ressourcen teilen. Das Betriebssystem braucht nur minimale Ressourcen zu investieren, um die Anwendung aufrechtzuerhalten (von der Prozessorleistung einmal abgesehen). Dieses Modell hat jedoch auch ein paar Nachteile. Das Betriebssystem muss die meisten Anwendungen bei sich registrieren, damit es Einstellungen pflegen, Ressourcengrenzen
30
Was ist .NET?
bestimmen und es den Programmen erlauben kann, mit sich und anderen Anwendungen zu interagieren, wenn es nötig ist. Das kann zu einem recht komplizierten Registrierungsvorgang führen, was wiederum umfangreiche Installationsprozeduren erfordert. Da jede Anwendung ihre eigenen Ressourcen zu verwalten hat, ist es zudem dem Entwickler überlassen, diese Ressourcen sauber zu handhaben. Man muss mitunter lästigen Code schreiben, nur um dafür zu sorgen, dass Ressourcen sauber geholt und wieder freigegeben werden, so dass es zu keinen Konflikten kommt. Die Common Language Runtime (CLR: etwa »Laufzeitversion in einer gemeinsamen Sprache«) ist die .NET-Form eines Managers oder Verwalters. Die CLR verwaltet alle Aspekte derjenigen Anwendungen, die in .NET laufen. Aufgrund dessen werden die Dinge in .NET ein wenig anders gehandhabt als bislang. Wenn Sie eine .NET-Anwendung erstellen, wird sie nicht zu einem direkt ausführbaren Programm kompiliert. Vielmehr wird Ihr Quellcode in etwas übersetzt, was als Microsoft Intermediate Language (MSIL) bezeichnet wird und im Grunde nur eine andere Methode ist, Ihren Code darzustellen. Wenn Sie später Ihr Programm ausführen, wird es von einem Just-In-Time (JIT) -Compiler in Maschinencode übersetzt, welcher wiederum von der CLR ausgeführt wird. Dieser zweistufige Vorgang ist zwar langsamer als der einstufige vor der Einführung von .NET, aber nicht so viel, wie Sie jetzt annehmen würden. Der JIT kann die MSIL sehr leicht verstehen. Daher lässt sie sich effizienter kompilieren als eine traditionelle Sprache. Und zum anderen bietet dieser Vorgang einen großen Vorteil gegenüber der ersten Methode: nämlich plattformübergreifende Kompatibilität. Maschinensprachen hängen von der jeweils verwendeten Maschine ab. Computer, die mit Windows und Intel-x86-Prozessoren arbeiten, sprechen andere Sprachen als solche mit Unix. Wenn man eine Anwendung auf eine andere Plattform portieren wollte, bedeutete es daher, mindestens den Quellcode neu zu kompilieren, damit er auf der neuen Plattform lief. Meistens erforderte es aber das Neu- oder Umschreiben des Codes. MSIL ist jedoch stets MSIL, ganz gleich, auf welcher Plattform sie sich befindet. Etwas, das in MSIL auf Windows funktioniert, wird auch in MSIL auf Unix funktionieren. Es ist lediglich jeweils ein entsprechender JIT-Compiler erforderlich, der den abschließenden Schritt des Kompilierens in Maschinensprache ausführt; aber darum haben Sie sich in der Regel nicht zu sorgen. Große Unternehmen wie Microsoft bieten höchstwahrscheinlich so einen Compiler für jede Plattform, bevor Sie eine Anwendung portieren müssen. Ein weiterer Vorteil des .NET-Paradigmas gegenüber dem herkömmlichen besteht darin, dass MSIL spezielle Attribute enthält, die als Metadaten bezeichnet werden und die es einem Programmcode erlauben, sich selbst gegenüber dem Betriebssystem zu beschreiben. Man erinnere sich daran, dass traditionelle Anwendungen sich oft selbst in einer zentralen Registrierdatenbank anmelden mussten, damit das Betriebssystem sie überwachen konnte. Das ist nun nicht mehr nötig, denn alle wichtigen Informationen liegen der Anwendung bei. Zu
31
Mit Windows Forms beginnen
solchen Informationen könnte gehören, nach welchen Abhängigkeiten man suchen soll, wo Sicherheitsanforderungen zu finden sind, Versionsnummern, Autoren usw. Die Bündelung solcher Informationen ist sehr hilfreich, wie Sie sehen werden, wenn Sie mit dem Codeschreiben anfangen. Nicht jede Programmiersprache oder jeder Compiler kann MSIL und Metadaten erzeugen. Daher hat Microsoft eine neue Klasse von Compilern geschaffen, die das .NET Framework unterstützt. Dadurch können alle Ihre bevorzugten Programmiersprachen .NET unterstützen. Mit der künftigen Verbreitung von .NET dürfen Sie weitere Compiler von anderen Unternehmen erwarten. Lassen Sie uns nun mal darauf achten, was nach dem Kompilieren passiert: Das Programm wird ausgeführt.
Die Common Language Runtime (CLR) Die CLR bildet das Zentrum des .NET Frameworks. Stellen Sie sie sich wie den Hauptprozessor für Ihre Anwendungen vor; sie behandelt die gesamte Ausführung und Verwaltung Ihrer Anwendungen. Sie hat zwar eine Menge Funktionen, doch Sie brauchen vorerst nur einige davon zu kennen. Zunächst einmal steuert die CLR den Ressourcenbedarf Ihrer Anwendung. Sie teilt deren Komponenten Speicher und Prozessorzeit zu und nimmt sie nach der Nutzung wieder zurück. Dieser Vorgang wird als Garbage Collection (Müllbeseitigung) bezeichnet, also als Speicherbereinigung. Entwickler mit einer gewissen Erfahrung, besonders in C und C++, werden sich an all die lästigen Routinen erinnern, die man schreiben musste, um Speicherressourcen zu verwalten. Diese Prozedur ist zum Glück nicht mehr nötig. Als zweiten Aspekt stellt die CLR eine vereinheitlichte Architektur bereit. Ganz gleich, welche Programmiersprache Sie nutzen, können Sie sich immer auf das gleiche Framework beziehen. C++-Anwendungen können leicht Komponenten nutzen, die in VB .NET erstellt wurden, und umgekehrt. Drittens verwendet die CLR Metadaten, die zusammen mit der jeweiligen MSIL erzeugt wurden, um eine saubere Ausführung zu gewährleisten. Ein Aspekt der Metadaten ist beispielsweise, dass sie Informationen über die Umgebung enthalten, also über Komponenten, Versionen usw., die verwendet wurden, um Ihre Anwendung zu erstellen. Die CLR kann diese Daten untersuchen, und wenn das aktuelle System nicht die gleichen notwendigen Ressourcen besitzt, kann die CLR diese Komponenten automatisch aktualisieren. Code, der für die CLR geschrieben wird, ist als verwalteter Code (Managed Code) bekannt: CLR verwaltet jeden Aspekt seiner Ausführung. Code, der nicht für die CLR bestimmt ist (also Prä-.NET-Anwendungen), wird als nichtverwalteter Code (Non-Managed Code)
32
Was ist .NET?
bezeichnet. Von jetzt ab verwenden wir diese Begriffe. Nichtverwalteter Code lässt sich weiterhin in der CLR ausführen, aber dafür müssen spezielle Vorkehrungen getroffen werden (darauf kommen wir am Tag 14 zu sprechen). Abbildung 1.3 zeigt die Pfade, denen verwalteter und nichtverwalteter Code in der CLR folgen. Verwalteter Code mit MSIL Die CLR Ressourcen zuweisen JIT Kompilierung
Metadaten untersuchen Sicherheitsprüfungen Ausführung
Nichtverwalteter Code Import in .NET
Garbage Collection (Müllbeseitigung)
Abbildung 1.3: Die CLR verwaltet die komplette Ausführung von verwaltetem Code und sorgt so für Sicherheit, Kompatibilität und richtige Funktionsweise.
Das .NET Framework installieren Bevor Sie die CLR einsetzen und Windows Forms schreiben können, müssen Sie zunächst das .NET Framework Software Development Kit (SDK) installieren. Das SDK gibt es kostenlos auf www.microsoft.com/net, aber beachten Sie, dass es über 100 Mbyte groß ist und das Herunterladen mit einem 56K-Modem über sechs Stunden dauern kann. Sie können es auch auf CD-ROM bei Microsoft bestellen (VisualStudio.NET liegt es bei). Das .NET Framework SDK erfordert Windows NT 4.0 mit Service Pack (SP) 6, Windows 2000 oder Windows XP, um richtig zu laufen. Wenn Sie ein anderes Betriebssystem besitzen, haben Sie Pech: Es gelingt Ihnen vielleicht, ein paar Beispiele zum Laufen zu bringen, aber Microsoft bietet Ihnen keinen Support, wenn das nicht gehen sollte. Sobald Sie ein Exemplar des SDKs haben, führen Sie das Setup-Programm aus (Abbildung 1.4). Klicken Sie auf WEITER, um die Installation zu starten, und akzeptieren Sie die Lizenzvereinbarung. Klicken Sie noch dreimal auf WEITER und lehnen Sie sich zurück, während sich Ihnen die Tore zur .NET-Welt öffnen (in anderen Worten: während das SDK installiert wird). Nach dem Abschluss der Installation erscheint ein Verknüpfungssymbol zu einer SDKÜbersicht auf Ihrem Desktop. Diese Webseite hat Verknüpfungen, um die .NET-Musterbeispiele einzurichten (falls Sie sie in einem vorhergehenden Schritt installiert haben) wie auch Verknüpfungen zu weiterer Dokumentation.
33
Mit Windows Forms beginnen
Abbildung 1.4: Der Installationsvorgang für das .NET Framework SDK beginnt mit dem Setup-Bildschirm.
Entwicklungsumgebungen In einer Entwicklungsumgebung verrichten Sie alle Ihre Arbeiten, wie etwa Anwendungserstellung und Eingeben von Code. Für die Entwicklung mit Windows Forms brauchen Sie jedoch lediglich ein Textprogramm, um Code zu schreiben. Zusätzlich zum Textprogramm bieten viele Entwicklungsumgebungen ausgetüftelte Benutzeroberflächen, die fortgeschrittene Fähigkeiten für die Verwaltung von Code und Projekten besitzen. Diese Funktionen sind häufig nützlich, aber nicht Bedingung. Der gesamte Quellcode in diesem Buch (und auf der entsprechenden Webseite www.samspublishing.com) liegt in einfachem Text vor. Das bedeutet, dass Sie einen Texteditor wie den Windows-Editor (NotePad) als Ihre Entwicklungsumgebung nutzen können. (Die von Ihnen erzeugten Dateien werden zwar die Erweiterungen .cs oder .vb tragen, aber dennoch aus einfachem Text bestehen.) Auch Visual Studio .NET ist ein ausgezeichneter Editor für die Anwendungen, die Sie erstellen. Es zeichnet Ihren Code in Farbe aus und stellt kontextabhängige Funktionen bereit, so etwa kleine Popup-Felder, die für Sie Sätze vervollständigen. Darüber hinaus stellt Visual Studio .NET zahlreiche Assistenten zur Verfügung, die Ihnen beim Erstellen von Windows Forms-Anwendungen beistehen. Obwohl es zahlreiche für Entwickler wertvolle Funktionen in Visual Studio .NET gibt, wird sich dieses Buch nicht darauf konzentrieren. Vielmehr beschäftigt es sich mit der Windows Forms-Technologie. Schließlich besteht die beste Methode, das Programmieren von Windows Forms zu erlernen, darin, sie selbst zu erstellen, und nicht, sie von einer grafischen Entwicklungsumgebung für Sie erstellen zu lassen. Sobald Sie darin versiert sind, selbst Quellcode zu schreiben, werden Sie die Leistungsmerkmale von Visual Studio .NET umso mehr zu schätzen wissen. Für dieses Buch brauchen Sie lediglich Notepad.
34
Was ist .NET Windows Forms?
In diesem Buch legen wir für jede Tageslektion einen neuen Ordner an, damit sich alle Dinge leicht wiederfinden lassen. Zum Beispiel werden die Dateien von Tag 2 im Ordner c:\winforms\day2 abgelegt. Wenn Sie also für die Beispiele in diesem Buch (oder aus dem Download von der Sams-Website) selbst Code schreiben, erstellen Sie diese Ordner und legen die Anwendungsdateien darin ab. Wenn Sie sich an dieses Bezeichnungsschema halten, können Sie dem weiteren Vorgehen leicht folgen.
1.3
Was ist .NET Windows Forms?
Sie fragen sich vielleicht, ob die Überschrift nicht besser »Was sind Windows Forms?« lauten sollte. Nein, denn Windows Forms ist keine Sammlung von Formularen bzw. Masken, die man in Windows benutzt, sondern eine einzige Technologie, die Ihnen beim Entwerfen von Windows-Anwendungen hilft. Sie ist Microsofts neue Plattform für die Entwicklung von Windows-Anwendungen, die auf dem .NET Framework basieren. Ein Blick auf die beiden Wörter »Windows Forms«: Sie kennen ja Windows bereits als Betriebssystem, das Benutzereingaben entgegennimmt und entsprechende Arbeiten ausführt. Ein Formular (form) ist genauso wie seine Papierversion etwas, das man mit Informationen ausfüllt, um damit etwas Bestimmtes zu erreichen. Ein Papierformular lässt sich beispielsweise dazu verwenden, einen Pass zu beantragen, eine Prüfung zu absolvieren oder sich zu bewerben. Ein digitales Formular wird üblicherweise eingesetzt, um Informationen einzugeben oder um mit einer Anwendung zu interagieren. Wenn Sie schon einmal das Internet benutzt haben, haben Sie bereits etliche digitale Formulare zu Gesicht bekommen. Viele Webseiten fragen auf diese Weise Informationen vom Benutzer ab, um ihn beispielsweise für eine Dienstleistung zu registrieren. Wahrscheinlich haben Sie bereits Dutzende, wenn nicht Hunderte solcher Formulare ausgefüllt. Abbildung 1.5 zeigt ein solches Formular. Der Vorteil eines digitalen Formulars gegenüber einem Papierformular besteht darin, dass das digitale mit Ihnen interagieren kann. Sobald Sie Informationen eintragen, kann das Formular die Eingabe verarbeiten oder Ihnen weitere Auswahlmöglichkeiten anbieten. Genau auf dieser Idee fußt die Windows Forms-Technologie. Sie verwendet digitale Formulare (die ebenfalls Windows Forms genannt werden), um Daten zu sammeln. Ein Windows Form ist ein Bildschirmfenster, mit dem der Benutzer interagiert. Es lässt sich für das Sammeln oder Anzeigen von Daten verwenden, für die Ausgabe von Alarmmeldungen usw. (Bitte beachten Sie, dass wir im Folgenden den Begriff sowohl für die Technologie als auch für das eigentliche Windows Forms-Fenster (= Formular) verwenden. Das ist zulässig, denn die Technologie besteht hauptsächlich aus diesen Fenstern.)
35
Mit Windows Forms beginnen
Abbildung 1.5: Ein typisches Webformular enthält Textfelder und eine Schaltfläche.
Diese Beschreibung könnte den Eindruck erwecken, dass ein Windows Form nicht sonderlich funktionsreich sei, doch in Wirklichkeit stellt es eine Menge Leistung und Flexibilität bereit. Weil die Windows Forms-Technologie auf dem .NET Framework beruht, steht ihr eine umfangreiche Bibliothek von nützlichen Objekten und Methoden zur Verfügung, die es einem Windows Forms-Fenster gestatten, so ziemlich alles auszuführen, was Sie wünschen. Daher mag ein Windows Form einfach nur eine Schnittstelle sein, um so mit dem Benutzer zu interagieren, doch die Technologie hinter dem Formular liefert die entsprechende Leistungsfähigkeit.
Integration mit .NET .NET und die CLR bieten Ihnen eine Fülle von Funktionen, um die Anwendungsentwicklung viel einfacher zu machen. Wie bereits angesprochen, stellt die Garbage Collection sicher, dass alle unbenutzten oder übrig gebliebenen Ressourcen, die Ihre Anwendung nicht mehr braucht, auf angemessene Weise behandelt werden und so für den Gebrauch durch andere Anwendungen freigemacht werden. Die Einbindung in .NET erlaubt es Ihren Windows Forms, die umfangreiche Klassenbibliothek des .NET Framework zu nutzen. Diese enthält fast alle Objekte, die Sie jemals für Ihre Anwendungen brauchen werden, in vorgefertigter und einsatzbereiter Form. Sie brauchen eine Komponente, die auf Datenbanken zugreift? .NET stellt Ihnen mehrere zur
36
Was ist .NET Windows Forms?
Auswahl. Wie wär's mit XML-Dateien? Die hat .NET ebenfalls. Das Erlernen der Windows Forms-Programmierung besteht also zum Teil aus dem Aufbau eines möglichst großen Repertoires solcher Objekte. Das ist der Faktor, der die Experten von den Neulingen unterscheidet. Und das Beste dabei: Dieses Framework steht Ihnen zur Verfügung, gleichgültig, welche Programmiersprache Sie wählen. Apropos Datenbankzugriff: Mit Windows Forms verfügen Sie über die Leistungsfähigkeit von ADO.NET, der nächsten Generation von ActiveX Data Objects (= ADO). Diese Weiterentwicklung führt viele Leistungsmerkmale ein, die die Reichweite und Flexibilität Ihrer Anwendung erhöhen. Sie können auf vorhandene Datenquellen zugreifen oder eigene erzeugen, können unverbundene Datensätze nutzen, XML-Dateien lesen und schreiben und anderes. Mehr über diese Leistungsmerkmale erfahren Sie an Tag 9. In das .NET Framework sind auch leistungsfähige Sicherheitsmerkmale integriert. Diese gestatten es dem Betriebssystem bereits dann zu erkennen, wie sicher Ihre Anwendung ist, wenn diese noch gar nicht ausgeführt worden ist. Es kann auch unbefugte Benutzer davon abhalten, auf Ihre Anwendungen zuzugreifen, ebenso wie es autorisierte Benutzer daran hindert, unautorisierte Anwendungen wie etwa Computerviren auszuführen. Auch das Konfigurieren und Installieren Ihrer Anwendungen ist ein Kinderspiel. Die Konfiguration erfordert häufig lediglich das Erstellen einer einzigen Textdatei mit der Endung .config. Implementierung bedeutet lediglich das Überspielen Ihrer Dateien auf den Zielcomputer; die Notwendigkeit, Ihre Anwendungen zu registrieren und zu installieren, entfällt. All dies ist dank CLR und Metadaten möglich. Wie der Zusatz ».NET« andeutet, ist Windows Forms eng mit dem Internet und Web Services integriert. Web Services sind Komponenten von online laufenden Anwendungen, mit denen Sie eine Verbindung herstellen können, um so deren Funktionsumfang zu nutzen. Daher brauchen Sie diese Funktionen nicht mehr selbst zu erstellen. GDI+ (Graphical Device Interface: Schnittstelle zu Grafikgeräten) erlaubt Ihnen, die grafischen Funktionen von Windows zu nutzen, um Ihre Anwendungen für die visuelle Interaktion fit zu machen. So können Sie mit GDI+ beispielsweise Anwendungen einfärben, Grafiken laden und bearbeiten und sogar Fenster transparent machen. Mehr zu GDI+ finden Sie an Tag 13. Wir werden alle diese Funktionen in den nächsten 20 Tagen behandeln, so dass Sie nach dem Durcharbeiten dieses Buches ein Experte in der Windows Forms-Programmierung sind.
Windows Forms-Steuerelemente Eine Windows Form ist für sich allein nicht sonderlich interaktiv. Wie Sie später an diesem Tag sehen werden, ist die Zahl der Möglichkeiten dessen, was ein Benutzer mit einem Formular tun kann, recht begrenzt. Windows Forms-Steuerelemente – die es alle in der
37
Mit Windows Forms beginnen
Klassenbibliothek des .NET Framework gibt – können Ihrer Anwendung erheblich mehr Dynamik verleihen, indem sie Ihnen interaktive Objekte bereitstellen, wie zum Beispiel Schaltflächen, Textfelder, Menüs und Statusleisten. Alle Windows Forms-Steuerelemente (Controls) arbeiten in einer gemeinsamen Architektur – das macht das Erlernen neuer Steuerelemente so einfach. Jedes Steuerelement lässt sich durch seine Eigenschaften vollständig steuern. Sollte es noch keine Steuerelemente geben, die Ihnen zusagen, so können Sie leicht Ihre eigenen erstellen. Die meisten verbreiteten HTML-Steuerelemente haben in Windows Forms ein Gegenstück. Ab morgen werfen Sie einen Blick auf einige Steuerelemente, umfassender gehen wir ab Tag 4 auf dieses Thema ein.
1.4
Die objektorientierte Vorgehensweise
Das .NET Framework ist vollständig objektorientiert. Und was bedeutet das? Objektorientierte Programmierung (OOP) ist ein Programmierparadigma, das eine logische Möglichkeit zur Anwendungsentwicklung bietet; es verknüpft Programmierbegriffe mit der Realität. In echter OOP ist alles ein »Objekt«, genauso wie alles, mit dem Sie täglich interagieren, ein Objekt ist. Ihr Auto, Ihre Kaffeemaschine, Ihr Stuhl – alle sind Objekte. Jedes Objekt verfügt über Eigenschaften, die es definieren. Ihre Auto etwa hat eine Farbe, gefahrene Kilometer, die Eigenschaft »Hersteller« und so weiter. Mit all diesen Eigenschaften lässt sich ein Objekt auf eindeutige Weise beschreiben. Meistens haben Sie in der OOP mit Objekten zu tun. Ein Windows Form ist ein Objekt, ebenso wie eine ASP.NET-Seite. Objekte verfügen auch über Methoden bzw. Aktionen oder Verhaltensweisen. Ihr Auto hat Methoden anzuspringen, zu bremsen, anzuhalten usw. Ein Windows Form hat Methoden zu starten, zu schließen, in der Größe verändert zu werden, die Farbe zu ändern und viele weitere Verhaltensweisen. Abbildung 1.6 zeigt Beispiele von Objekten und einige ihrer Eigenschaften und Methoden. Darüber hinaus findet man oft Objekte, die auf dem gleichen grundlegenden Konzept basieren. Sowohl Pkws als auch Lastwagen sind Motorfahrzeuge, wenn auch im Detail verschieden. Manchmal gibt es Objekte, die auf einem anderen Objekt basieren; so etwa stammt der neue VW Käfer vom ursprünglichen Käfer ab. Dies sind Prinzipien (nämlich Schnittstellen bzw. Vererbung), die sich auch in der OOP wiederfinden. Sie bieten Ihnen die Flexibilität, um Ihre vorhandenen Objekte zu erweitern, wenn sie nicht genau Ihre Wünsche erfüllen.
38
So erstellen Sie Ihre erste Anwendung
“Auto”-Objekt Eigenschaften: Farbe, Hersteller Methoden: Start, Tür öffnen
“Scheinwerfer”-Objekt Eigenschaften: Helligkeit, Farbe Methoden: ein-/ausschalten
“Rad”-Objekt Eigenschaften: Größe, Luftdruck Methoden: Abrieb des Gummis
Abbildung 1.6: Ein Auto weist viele Eigenschaften und Methoden auf, wie auch Unterobjekte, die ihrerseits über Eigenschaften und Methoden verfügen.
Windows Forms basiert auf Prinzipien der OOP: Jeder Teil einer Windows Forms-Anwendung ist ein Objekt, vom sichtbaren Fenster bis hin zu den anklickbaren Schaltflächen, den ausfüllbaren Textfeldern und sogar bis zur Tageszeit, zu der Sie die Anwendung nutzen. Selbst wenn Sie noch nie OOP eingesetzt haben, dürfte Ihnen dies einleuchten. Solange Sie sich alles als ein selbstständiges Objekt vorstellen, werden Sie schnell lernen. Da alles in .NET ein Objekt ist, werden Sie, nachdem Sie den Umgang mit einem Objekt kennen, Bescheid wissen, wie Sie dieses Wissen auf alle übrigen Objekte übertragen. Das macht Ihr Leben als Entwickler leichter. Wir werden uns diesem Thema an Tag 3 widmen, nachdem Sie ein paar Beispiele für Objekte in Windows Forms gesehen haben.
1.5
So erstellen Sie Ihre erste Anwendung
Höchste Zeit, sich ein bisschen in Code zu vertiefen! Geben Sie den Code aus Listing 1.1 in Ihren Editor ein und speichern Sie ihn als Datei c:\winforms\day1\helloworld.vb. Denken Sie daran, im Dialogfeld SPEICHERN UNTER den Eintrag Alle Dateien im Feld DATEITYP auszuwählen und nicht TXT-Dateien. Sonst wird nämlich Ihre Datei als helloworld.vb.txt gespeichert! Listing 1.1: Ihre erste Visual Basic .NET Windows Forms-Anwendung! 1: 2: 3: 4: 5: 6: 7:
Imports System Imports System.Windows.Forms Public Class HelloWorld : Inherits Form Public Shared Sub Main() Application.Run(New HelloWorld)
39
Mit Windows Forms beginnen
8: 9: 10: 11: 12: 13: 14:
End Sub Public Sub New() Me.Text = "Hallo Welt!" End Sub End Class
Keine Sorge, wenn Sie diesen Code nicht verstehen sollten. Sie erfahren morgen, was dies bedeutet (wir untersuchen morgen auch C#-Code). Ein kurzer Überblick soll vorerst genügen: Die Zeilen 2 und 3 verwenden eine spezielle Syntax, um Ihrer Anwendung zu gestatten, die Namensräume oder Objektgruppen (aus der .NET-Klassenbibliothek) System und System.Windows.Forms zu nutzen. Das Schlüsselwort Imports referenziert diese Namensräume zwecks einfacherer Verwendung. In Zeile 4 deklarieren Sie Ihre Klasse bzw. Ihr Objekt. Die Zeilen 6-8 stellen den Startpunkt für Ihre Anwendung dar und die Zeilen 10-12 sind ein so genannter Konstruktor. In Zeile 14 schließen Sie Ihre Klasse ab. Ein Großteil dieses Codes konfiguriert Ihre Anwendung. Die einzige echte Ausführungsanweisung finden Sie in Zeile 11, die aussieht, als ob sie irgendwo in der Anwendung den Gruß »Hallo Welt!« anzeigen würde. Der nächste Schritt besteht darin, Ihre Anwendung zu MSIL zu kompilieren. Öffnen Sie ein Fenster mit einer Befehlseingabeaufforderung (START / (ALLE) PROGRAMME / ZUBEHÖR / EINGABEAUFFORDERUNG) und navigieren Sie zu dem Ordner, in welchem Sie diese Datei gespeichert haben. Geben Sie den folgenden Befehl in das Fenster ein und drücken Sie (¢): vbc /t:winexe /r:System.dll /r: System.Windows.Forms.dll helloworld.vb
Kümmern Sie sich hier noch nicht um die Syntax. Es genügt, dass Sie wissen, dass dieser Befehl eine Visual Basic .NET-Anwendung zu einer ausführbaren MSIL-Datei kompiliert. Sie sollten folgende Ausgabe sehen: C:\winforms\day1>vbc /t:winexe /r:system.dll [ic:ccc] /r: system.windows.forms.dll helloworld.vb Microsoft (R) VisualBasic.NET Compiler version 7.00.9254 for Microsoft (R) .NET CLR version 1.00.2914.16 Copyright (C) Microsoft Corp 2001. All rights reserved. C:\winforms\day1>
Falls keine Fehlermeldungen angezeigt werden, steht Ihre Anwendung zur Ausführung bereit. (Falls Sie helloworld.exe nicht finden, suchen Sie die Datei mit Ihrer Suchfunktion.) Geben Sie an der Eingabeaufforderung helloworld ein; es sollte dann ein Fenster wie in Abbildung 1.7 zu sehen sein.
40
Zusammenfassung
Abbildung 1.7: Hier sehen Sie Ihre erste Windows Forms-Anwendung namens »Hallo Welt!«
Diese Anwendung ist zwar noch nicht reich an Funktionen, beachten Sie aber bitte die Titelleiste »Hallo Welt!«. Herzlichen Glückwunsch zur Fertigstellung Ihrer ersten, wenn auch bescheidenen Windows Forms-Anwendung!
1.6
Zusammenfassung
Um Sie mit dem .NET Framework und Windows Forms bekannt zu machen, war dies heute nur eine kurze Lektion. Man muss zuerst über diese grundlegenden Themen sprechen, so dass der Code später einen Sinn ergibt, wenn man ihn sich genauer ansieht. Heute haben Sie gelernt, wie das Betriebssystem mit Ihren Anwendungen und Benutzereingaben zusammenarbeitet. Das .NET Framework ist eine neue Architektur, um damit Windows-Anwendungen zu erstellen. Damit können Sie unter Verwendung jeder beliebigen Programmiersprache eine Anwendung erstellen, wohlgemerkt: stets auf der gleichen standardisierten Architektur. Mit der Common Language Runtime (CLR) lassen sich Ihre Anwendungen zu Microsoft Intermediate Language (MSIL) kompilieren statt direkt zu einer ausführbaren Datei. MSIL enthält Metadaten, die wiederum der CLR Informationen über Ihre Anwendung, darunter Versionsnummer und Sicherheitsangaben, zur Verfügung stellen. Die Metadaten befreien Sie von der Notwendigkeit, Ihre Anwendung bei einem Betriebssystem anzumelden. Windows Forms ist als Teil von .NET ein Framework (Gerüst) für die Erstellung von Windows-Anwendungen, die die CLR nutzen. Sein Vorteil gegenüber herkömmlicher Windows-Programmierung liegt in der vereinheitlichten, objektorientierten Architektur, die die Klassenbibliothek des .NET Frameworks nutzen kann. Von Ihrer ersten Windows Forms-Anwendung haben Sie gelernt, dass der Quellcode eine einfache Textdatei ist und sich im Editor erstellen lässt. Er muss zu MSIL kompiliert werden, bevor man ihn verwenden kann; Sie haben das getan, indem Sie einen Compiler an der Eingabeaufforderung benutzten.
41
Mit Windows Forms beginnen
1.7 F
Kann ich wirklich jede gewünschte Programmiersprache benutzen, um Windows Forms zu erstellen? A
F
Fragen und Antworten
In der Theorie schon, aber in der Praxis nicht ganz. Sie brauchen einen Compiler, der Ihren Code in MSIL umwandelt und Metadaten ausgibt. Die einzigen Sprachen, die dies zur Zeit beherrschen, sind C++, C# und Visual Basic .NET, obwohl die Unterstützung für viele weitere Sprachen gerade entwickelt wird.
Was versteht man unter ASP.NET? A
1.8
ASP.NET ist eine weitere Facette von .NET. Es erlaubt Ihnen, interaktive Webseiten und Web Services zu erstellen. ASP.NET ist Windows Forms sehr ähnlich – beide haben die gleichen Begriffe und Frameworks gemeinsam –, doch ASP.NET wird verwendet, um Internetanwendungen zu schreiben, während Windows Forms für Windows-Anwendungen eingesetzt wird.
Workshop
Dieser Workshop soll Ihnen helfen, sich besser an die heute behandelten Begriffe zu erinnern. Es hilft Ihnen sehr, die Antworten vollständig zu verstehen, bevor Sie weiterlesen. Die Antworten zu den Quizfragen und den Übungen finden Sie in Anhang A.
Quiz 1. Wahr oder falsch? Windows Forms ist Win32-Programmierung. 2. Wahr oder falsch? Metadaten enthalten Angaben darüber, in welcher Umgebung eine Anwendung erstellt wurde. 3. Auf welche Weise ermöglicht MSIL plattformübergreifende Funktionsweise? 4. Wie nennt man die Weiterentwicklung von ActiveX Data Objects? 5. Wahr oder falsch? Man kann Windows Forms-Anwendungen mit jedem beliebigen Texteditor erstellen. 6. Was ist ein Windows Forms-Steuerelement? 7. Was bewirkt das Imports-Statement?
42
Workshop
Übung Was würde der folgende Code bewirken, sobald er kompiliert und ausgeführt würde? 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15:
Imports System Imports System.Windows.Forms Public Class MyApp : Inherits Form Public Shared Sub Main() Application.Run(New MyApp) End Sub Public Sub New() Me.Height = 100 Me.Width = 50 End Sub End Class
43
Windows FormsAnwendungen erstellen
2
Windows Forms-Anwendungen erstellen
Nachdem Sie die Grundlagen verstanden haben, ist es nun an der Zeit, sich kopfüber in die Erstellung von Windows Forms-Anwendungen zu stürzen. In der heutigen Lektion werfen Sie einen weiteren Blick auf die gestrige Anwendung und konzentrieren sich dabei auf die verschiedenen Elemente einer Windows Forms-Anwendung. Sie betrachten die Erstellung von Windows Forms mit C#, derjenigen Sprache, die viele Entwickler für Windows Forms bevorzugen. Sie untersuchen die Unterschiede im Code, den VB .NET und C# produzieren. Am Ende der heutigen Lektion werden Sie die eine Sprache leicht in die andere übersetzen können. Heute lernen Sie, 쐽
was Klassen, Assemblies und Namensräume sind,
쐽
wie Vererbung funktioniert,
쐽
auf welche Weise man die Main-Methode einsetzt,
쐽
was Konstruktoren und Destruktoren sind,
쐽
wie Sie Quellcode kompilieren,
쐽
in welcher Weise Sie alles zusammensetzen, um eine Anwendung zu erhalten.
2.1
Ein weiterer Blick auf Ihre Anwendung
Bitte erinnern Sie sich an die gestrige Zusammenfassung. Alles Weitere baut darauf auf. Nun lassen Sie uns den gestrigen Code betrachten, den Sie in Listing 2.1 finden. Listing 2.2 zeigt den gleichen Code in C#. Weil alle Windows Forms-Anwendungen das gleiche .NET Framework verwenden, sind die Quellcodes in Visual Basic .NET und C# fast identisch, abgesehen von ein paar syntaktischen Unterschieden. Alle Konzepte in dem einen Quellcode beziehen sich auch auf den anderen. Listing 2.1: Ihre erste Visual Basic .NET-Windows Forms-Anwendung 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11:
46
Imports System Imports System.Windows.Forms Public Class HelloWorld : Inherits Form Public Shared Sub Main() Application.Run(New HelloWorld) End Sub Public Sub New() Me.Text = "Hello World!"
Ein weiterer Blick auf Ihre Anwendung
12: 13: 14:
End Sub End Class
Listing 2.2: Ihre erste C#-Windows Forms-Anwendung 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14:
using System; using System.Windows.Forms; public class HelloWorld : Form { public static void Main() { Application.Run(new HelloWorld()); } public HelloWorld() { this.Text = "Hello World!"; } }
Beachten Sie, dass C# auf Groß-/Kleinschreibung achtet, so dass der Code genau so wie in Listing 2.2 geschrieben sein muss, sonst erhalten Sie Fehlermeldungen. Wir gehen hier nicht näher auf den Code ein, aber Ihnen werden einige Ähnlichkeiten auffallen. Es finden sich Namensräume, Klassen, Methoden und eine Main-Methode in beiden Listings. In den folgenden Abschnitten gehen wir auf diese einzelnen Begriffe ein.
Klassen Wenn Sie noch keine OOP-Sprache verwendet haben, dürfte Ihnen der obenstehende Code merkwürdig erscheinen. Lassen Sie uns zunächst Zeile 4 in beiden Listings betrachten, denn sie enthält die wichtigsten Konzepte. Diese Zeile deklariert unsere Klasse namens HelloWorld. Eine Klasse ist im Wesentlichen die Definition eines Objekts. In der Klasse bestimmen Sie die individuellen Eigenschaften sowie die Funktionen, die andere Objekte vielleicht einmal nutzen. Stellen Sie sich eine Klasse wie eine Blaupause oder einen Bauplan für ein Haus vor. Die Blaupause selbst ist nicht gerade reich an Funktionen; man kann kaum etwas damit tun. Sie legt jedoch alles fest, was das Haus benötigt: wohin die Türen und die Lampen kommen, die Zimmergröße, die Leitungen und das ganze übrige Innenleben eines Hauses. Ein Bauleiter benutzt die Blaupause bzw. einen Bauplan, um das tatsächliche Haus zu
47
Windows Forms-Anwendungen erstellen
erbauen, das ein gebrauchsfertiges Objekt darstellt: Sie können die Türen öffnen und schließen, die Wandfarbe ansehen (und ändern) usw. Ähnlich wie eine Blaupause legt eine Klasse das ganze Innenleben eines Objekts fest. In unserem Fall entspräche der Bauleiter der CLR, denn sie untersucht die Blaupausen und wandelt sie zu etwas Brauchbarem um: zu einem Objekt oder einer Anwendung. Somit können die Benutzer Ihres Objekts Methoden ausführen, die Sie festgelegt haben, und verfügbare Eigenschaften untersuchen. Kurz und gut: Sie erstellen eine Klasse, die von der CLR in etwas Nützliches umgewandelt wird. Der jeweilige Benutzer des Objekts braucht sich nicht darum zu kümmern, wie das Innere definiert ist, sondern benutzt es einfach – genau wie ein Hausbewohner im Allgemeinen nicht wissen muss, wie die Klimaanlage funktioniert, solange sie nur zuverlässig ihren Dienst versieht. Diese Abstraktion von Komplexität wird als Kapselung bezeichnet. Mit Hilfe von Klassen kapseln Sie die Einzelheiten und Implementierungsdetails, so dass sich der Benutzer nicht darum zu kümmern braucht. Abbildung 2.1 illustriert den Begriff der Kapselung. Public Class Hello … … end class
1. Was der Benutzer nicht zu sehen bekommt
2. Die CLR erzeugt das Objekt
Object
3. Was der Benutzer sieht und verwendet
Abbildung 2.1: Die CLR kapselt Ihre Klassenbaupläne in einem Objekt.
Wenn Ihnen diese Vorstellung immer noch nebulös vorkommt, sorgen Sie sich nicht. Wenn wir diese und andere Klassen untersuchen, werden Sie rasch ein grundlegendes Verständnis entwickeln. Die Wörter »Klasse« und »Objekt« werden oft synonym verwendet. Solange Sie den Unterschied kennen – eine Klasse legt die innere Funktionsweise eines Objekts fest –, ist das in Ordnung. Zurück zu Zeile 4: Wir müssen noch zwei Teile untersuchen. Das Schlüsselwort Public bedeutet, dass diese Klasse für andere Klassen zugänglich ist. Wenn Sie umgekehrt das Schlüsselwort Private benutzen, kann keine andere Klasse diese Klasse sehen oder benutzen. Wenn sie nicht öffentlich wäre, könnte sie Ihre Anwendung nicht nutzen. Es lassen sich hier ein paar weitere Schlüsselwörter verwenden, so etwa Protected oder Friend, aber wir werden diese zu gegebener Zeit betrachten.
48
Ein weiterer Blick auf Ihre Anwendung
Der letzte Bestandteil von Zeile 4, nämlich Inherits Form, ist etwas Besonderes für objektorientierte Programme (die C#-Version verwendet das Schlüsselwort Inherits nicht, sondern einfach einen Doppelpunkt). Alles in .NET ist ein Objekt (oder eine Klasse). Daher gibt es auch eine Klasse namens Form. Sie ist die Basisklasse für alle Windows FormsAnwendungen: Sie enthält Funktionen, die es gestatten, dass ein Formular angezeigt, geschlossen, bewegt und skaliert werden kann. Daher muss jede von Ihnen erstellte Windows Forms-Anwendung auf diese Form-Klasse zugreifen. Das Schlüsselwort Inherits teilt der CLR mit, dass alle Eigenschaften und Methoden in dieser Form-Klasse automatisch Ihrer Klasse HelloWorld hinzugefügt werden sollen. Das bedeutet, die Klasse HelloWorld erbt sie von der Klasse Form. Ebenso wie Sie Ihre blauen oder braunen Augen von Ihren Eltern geerbt haben, erbt die HelloWorld-Klasse von ihrer Elternklasse Form die Fähigkeit, ein Fenster am Bildschirm anzuzeigen. Die Form-Klasse erbt ihrerseits von einer anderen Klasse namens ContainerControl, welche von der Klasse Control erbt, welche ihrerseits von der Klasse Component erbt und so weiter und so fort. Allen liegt die Object-Klasse zu Grunde, die Urahnin aller .NET-Klassen. Sie enthält Eigenschaften, die alle anderen Klassen gemeinsam nutzen. Wenn Sie also wissen, wie Sie mit einem Objekt interagieren, können Sie demzufolge auch mit allen seinen Abkömmlingen interagieren. Jeder Abkömmling erbt Merkmale von allen seinen Vorgängern. Vererbung erspart uns eine ganze Menge Codeschreiben bzw. Umschreiben. Vererbung erleichtert auch das Erlernen neuer Klassen. Sofern Sie wissen, wie Sie mit ihren Basisklassen umgehen, verstehen Sie auch mit einer neuen Klasse umzugehen. An Tag 3 werden wir die Klasse Form genauer unter die Lupe nehmen. Da Sie nun über Klassen Bescheid wissen, sollten Sie auch erfahren, dass auch die Form-Klasse Public ist. Sonst wäre Ihre Klasse HelloWorld nicht in der Lage, darauf zuzugreifen. Nur nicht-Private Mitglieder der Form-Klasse werden vererbt. Nicht nur Klassen können erben, sondern auch Methoden, und zwar mit der gleichen Syntax. Klassen und Vererbung sind die zwei wichtigsten Konzepte in der OOP. Sobald Sie diese einmal verstanden haben, fällt Ihnen der Rest wesentlich leichter. Über diese Themen erfahren Sie mehr im ganzen restlichen Buch.
Namensräume und Assemblies Die Zeilen 1 und 2 in Listing 2.1 enthalten ein weiteres wesentliches Element von Windows Forms-Anwendungen: das Schlüsselwort Imports (in C# lautet es using, wie Sie in Listing 2.2 sehen). Wie Sie bereits erfahren haben, erlaubt Ihnen dieses Schlüsselwort, Sammlungen anderer Klassen in Ihrer Anwendung zu verwenden. Solche Sammlungen werden als Namensräume bezeichnet. Sie dienen dazu, miteinander verwandte Klassen zu gruppieren.
49
Windows Forms-Anwendungen erstellen
In Zeile 1 importiert die Anwendung den Namensraum System. Innerhalb des Namensraumes System befinden sich zahlreiche Objekte und Klassen, die Ihre Anwendung nutzt, selbst wenn Sie es nicht wissen. Beispielsweise sind die gebräuchlichen Datentypen Integer und String Bestandteil des Namensraumes System. Wenn Sie also eines dieser Objekte nutzen, ist es gut, den Namensraum System zu importieren (zwar eine gute Idee, aber nicht zwingend). Die Zeile 2 importiert den Namensraum System.Windows.Forms, welcher die Klasse Forms enthält. Der Forms-Namensraum ist einer von vielen im Windows-Namensraum, und dieser ist wiederum nur einer von vielen im System-Namensraum. Namensräume sind hierarchisch organisiert, um ihre Nutzung intuitiv und einfacher zu machen, insbesondere dann, wenn man schon die OOP-Grundbegriffe versteht. Ein paar der gebräuchlicheren Namensräume sind: 쐽
System
쐽
System.Data
쐽
System.Drawing
쐽
System.Net
쐽
System.Security
쐽
System.Web
쐽
System.Web.UI
쐽
System.Web.Services
쐽
System.Windows
쐽
System.Windows.Forms
쐽
System.Xml
Der Umstand, dass Namensräume hierarchisch untereinander angeordnet sind, bedeutet nicht notwendigerweise, dass der eine irgendetwas vom anderen erbt. Namensräume sind im Unterschied zu Klassen lediglich Sammlungen von Objekten – aus ihnen werden keine nutzbaren Objekte erstellt. Man verwendet sie lediglich aus organisatorischen Gründen. Natürlich können Sie leicht Ihre eigenen Namensräume erstellen. Es mag an diesem Punkt noch nicht nötig sein, aber sobald Sie komplexere Anwendungen erzeugen, die aus mehr als einer Quellcodedatei bestehen, wollen Sie sie vielleicht in einem Namensraum gruppieren. Beispielsweise so: Namespace MyNamespace Public Class MyClass : Inherits Form 'etwas mehr Code End Class End Namespace
50
Ein weiterer Blick auf Ihre Anwendung
Der restliche Code bleibt genau, wie er ist. Sie haben ihn nur mit einem Namensraum umgeben. In diesem Fall wird der Namensraum MyNamespace genannt. Sie können ihn auch vorhandenen Namensräumen hinzufügen: Namespace System.Windows.Forms
Es hängt alles davon ab, wie Sie diese Dinge gruppieren möchten. Wie gesagt, brauchen Sie Namensräume nicht unbedingt zu importieren. Dieses Vorgehen erlaubt Ihnen jedoch, weiter unten in Ihrem Code Kurzbefehle, also Abkürzungen, zu verwenden. Wenn Sie beispielsweise Zeile 2 aus Listing 2.1 entfernen würden, müssten Sie Zeile 4 von Public Class HelloWorld: Inherits Form
in Public Class HelloWorld: Inherits System.Windows.Forms.Form
ändern. Mit anderen Worten: Sie hätten dann den gesamten Namensraum-Namen für die Form-Klasse zu spezifizieren. Das Gleiche gilt für die Klasse System: Wenn Sie sie nicht importieren, würden Ihre Datentypen wie System.Integer statt nur wie Integer aussehen. Ich empfehle
Bitte beachten Sie
Importieren Sie Namensräume mit dem Schlüsselwort Imports, wenn Sie Klassen verwenden werden, die in diesem Namensraum enthalten sind; das hilft bei der Organisation und spart Zeit.
Verlassen Sie sich nicht auf die Verwendung des kompletten Namensraum-Namens für alle Ihre Objekte, selbst wenn Sie meinen, dass Ihnen das hilft, Klassen zu unterscheiden. Dieses Vorgehen bläht Ihren Code auf und erschwert es, auf Ihr Objekt in der Hierarchie zu verweisen. Gelegentliche Verwendung ist in Ordnung, aber übertreiben Sie es nicht.
Wenn Sie in Ihrer Anwendung keinen Namensraum festlegen, wird automatisch ein Standardnamensraum – auch als »Globaler Namensraum« bezeichnet –erzeugt. Er hat keinen eigentlichen Namen und dient lediglich dazu, die Klassen zu gruppieren, die nicht explizit in einem Namensraum deklariert wurden. Namensräume sind also logische Gruppierungen verwandter Klassen, aber das sagt noch lange nichts über ihre physische Position aus. Sie können diverse Quellcode-Dateien in verschiedenen Teilen Ihrer Festplatte abgelegt haben, aber alle können demselben Namensraum angehören. Um mehrere Dateien und Namensräume an einer einzigen Stelle zusammenzufassen, benutzt man Assemblies. Das sind physische Dateien auf Ihrer Festplatte, die die von Ihnen erstellten Klassen enthalten (meist mit der Endung .dll). Sie können mehrere Klassen oder Namensräume in einer einzelnen Assembly unterbringen.
51
Windows Forms-Anwendungen erstellen
Beispielsweise werden Sie in Ihrem Ordner c:\winnt\Microsoft.NET\Framework\version\ die Datei system.dll entdecken. Diese Assembly enthält die Namensräume System, System.CodeDom (und dessen Unternamensräume), System.ComponentModel, System.Net (und dessen Unternamensräume) und noch ein paar andere. Wie man sie verwendet, untersuchen wir etwas später heute im Abschnitt »Das Kompilieren von Windows Forms«. Tabelle 2.1 listet die verschiedenen .NET-Assemblies und die darin enthaltenen Namensräume auf. Assembly
Namensräume
custommarshalers.dll
Runtime.InteropServices.CustomMarshalers
mscorlib.dll
Collections, Configuration.Assemblies, Diagnostics.SymbolStore, Globalization, IO, IO.IsolatedStorage, Reflection, Reflection.Emit, Resources, alle Runtime.*-Namensräume (außer jenen in CustomMarshalers.dll, System.Runtime.Remoting.dll und System.Runtime.Serialization.Formatters.Soap.dll), Security, Security.Cryptography, Security.Cryptography.X509Certificates, Security.Permissions. Security.Policy, Security.Principal, Text, Threading
system.dll
System, CideDom, CodeDom.Compiler, Collections.Specialized, ComponentModel, ComponentModel.Design, Configuration, Diagnostics, Net, Net.Sockets, Text.RegularExpressions, Timers
system.configuration.install.dll
Configuration.Install
system.data.dll
Data, Data.Common, Data.OleDb, Data.SqlClient, Data.SqlTypes
system.design.dll
ComponentModel.Design.Serialization, Web.UI.Design, Web.UI.Design.WebControls, Windows.Forms.Design
system.directoryservices.dll
DirectoryServices
system.drawing.dll
Drawing,Drawing.Drawing2D, Drawing.Imaging, Drawing.Printing, Drawing.Text
system. drawing.design.dll
Drawing.Design
system.enterpriseser- EnterpriseServices, System.EnterpriseServices.CompensatingResourcevices.dll Manager system.management.dll Management, Management.Instrumentation system.messaging.dll
Messaging
Tabelle 2.1: NET-Assemblies und -Namensräume
52
Ein weiterer Blick auf Ihre Anwendung
Assembly
Namensräume
system.runtime. remoting.dll
Runtime.Remoting.Channels, Runtime.Remoting.Channels.Http, Runtime.Remoting.Channels.Tcp, Runtime.Remoting.MetadataServices system.Runtime.Serialization.Formatters.Soap.dll
Runtime.Serialization.Formatters.Soap system.security.dll
Security.Cryptography.Xml
system.serviceprocess.dll
ServiceProcess
system.web.dll
Web, Web.Caching, Web.Configuration, Web.Hosting, Web.Mail, Web.Security, Web.SessionState, Web.UI, Web.UI.HtmlControls, Web.UI.WebControls
system.web.services.dll
Web.Services, Web.Services.Configuration, Web.Services.Description, Web.Services.Discovery, Web.Services.Protocols
system.windows.forms.dll
Windows Forms
system.xml.dll
Xml, Xml.Schema, Xml.Serialization, Xml.Path, Xml.Xsl
(Soweit nicht anders spezifiziert, sind alle Namensräume Sub-Namensräume des Namensraums System.) Tabelle 2.1: NET-Assemblies und -Namensräume (Forts.)
Es gibt eine Menge Namensräume in Tabelle 2.1, von denen Sie manche in diesem Buch nie verwenden werden. Häufig müssen Sie aber wissen, zu welchen Assemblies sie gehören, um Ihre Anwendungen kompilieren zu können. Wenn Sie mit älteren Methoden der Windows-Programmierung vertraut sind, dürften Sie die .dll-Endung wiedererkennen, die für Dynamically Linked Libraries steht – diese DLLs sind Assemblies ähnlich. Sie stellten Objekte bereit, die von anderen Anwendungen genutzt werden konnten. DLL-Dateien werden sowohl in .NET als auch in Prä-.NET gebraucht. Denken Sie aber daran, dass .NET-DLL-Dateien in MSIL geschrieben und völlig objektorientiert sind. Prä-.NET-DLLs sind weder das eine noch das andere und leider gibt es keine Methode, um den Unterschied schon auf den ersten Blick zu erkennen. Assemblies benutzt man nicht nur für das Gruppieren von Namensräumen. Sie sind eine wichtige Komponente des .NET Frameworks, da sie die Grenzen zwischen Anwendungen darstellen, mit denen die CLR die Gültigkeitsbereiche Sicherheit, Versionierung und Metadaten erzwingt. Beim Entwickeln mit Windows Forms werden Sie Assemblies sehr häufig verwenden.
53
Windows Forms-Anwendungen erstellen
Die Main-Methode Sehen wir uns noch einmal die Zeilen 6, 7 und 8 in Listing 2.1 an: 6: 7: 8:
Public Shared Sub Main() Application.Run(New HelloWorld) End Sub
Wenn Sie wissen, was Methoden sind, haben Sie diesen Code bereits zur Hälfte verstanden. Eine Methode ist ein Codestück, das eine Funktion ausführt; sie bewirkt etwas. Code, der irgendeine Funktion ausführt, von der einfachsten Addition bis zu den kompliziertesten Threading-Routinen, muss immer in einer Methode untergebracht werden. In Visual Basic .NET gibt es zwei Methodentypen: Sub und Function. Sub macht etwas und endet dann. Function hingegen bewirkt etwas und sendet dann Daten an diejenige Instanz zurück, die die Methode aufgerufen hat. (Nichts hindert Sie, eine Function ohne Rückgabewert aufzusetzen; aber das Umgekehrte geht nicht: Eine Sub-Methode kann keine Daten zurückgeben.) In C# hingegen ist eine Methode eine Methode. Es wird keine Unterscheidung danach gemacht, ob die Methode Daten liefert oder nicht. Sie müssen in C# jedoch den Datentyp der zurückzugebenden Daten spezifizieren oder void, wenn keine Daten zurückgegeben werden. Beispielsweise sagt die Zeile 6:
public static void Main() {
in Listing 2.2 aus, dass diese Methode nichts liefert, wohingegen die folgende Codezeile einen Integer zurückgibt: 6:
public static int Main() {
Sie können automatisch ein paar Schlüsse ziehen: Sie deklarieren eine void-Funktion (sub in VB .NET), sie wird Main genannt und ist public (Public in VB .NET), was bedeutet, dass andere Klassen darauf zugreifen können. Das Schlüsselwort static (bzw. Shared) ist hingegen etwas Neues. Um es zu verstehen, müssen wir wieder zurückgehen zu Objekten und Klassen.
Objektinstanzen Aus einer einzelnen Klasse (oder Blaupause) lassen sich mehrere Objekte dieser Klasse erzeugen. Jedes einzelne Objekt bezeichnet man als eine Instanz. Das Auto-Objekt – eine wirkliche, physische, gebrauchsfertige Sache – wäre eine Instanz des Auto-Konzepts oder der Klasse. Man kann also ein Objekt haben, aber viele Instanzen: einen Mercedes SLK, einen VW Käfer oder einen Opel Tigra – all dies sind Instanzen eines Autos. Sie gleichen einander nicht ganz, aber sie alle haben das grundlegende Konzept eines Autos gemeinsam.
54
Ein weiterer Blick auf Ihre Anwendung
Abbildung 2.2 zeigt zwei Instanzen des Objekts »Standuhr«. Beachten Sie, dass sie nicht die gleiche Zeit anzeigen, doch irgendwie weiß man, dass beide Standuhren sind. Somit kann man viele Instanzen eines Objekts haben, von denen jede unterschiedliche Eigenschaften besitzt.
Standuhr A: 1:27
Standuhr B: 9:54
Abbildung 2.2: Zwei Instanzen eines Standuhr-Objekts können unterschiedliche Eigenschaften besitzen.
Das Gleiche lässt sich über OOP und Objekte sagen. Die Klasse definiert den theoretischen Begriff eines Objekts HelloWorld, erzeugt aber keine Instanzen. Es ist anderen Objekten überlassen, diese Instanzen zu erzeugen, wenn sie gebraucht werden (gleich mehr darüber). Wenn Sie vor einer Methode das Schlüsselwort Shared oder static verwenden, bedeutet dies, dass die Methode oder Eigenschaft allen Instanzen dieses Objekts zur Verfügung steht und für alle Instanzen gleich ist. Beispielsweise sind alle Autos aus Blech gefertigt und der Einfachheit halber sagen wir, dass das Blech das gleiche für jedes Auto ist. Dies wäre eine static-Eigenschaft: Die Metallart ändert sich nicht von Auto zu Auto. Alle Autos nutzen außerdem das Konzept der Farbe gemeinsam, haben aber nicht alle die gleiche Farbe; dies wäre dann nicht static. Die Schlüsselwörter sind selbsterklärend: Einzelwerte oder einzelne Methoden können Shared sein (in VB .NET), wenn alle Objekte sie gemeinsam nutzen (jedes Objekt hat nicht seine eigene Kopie), oder static (in C#), was bedeutet, dass sie sich nicht von einer Instanz zur nächsten ändern. Die Begriffe Shared und static sind im .NET Framework recht gebräuchlich. Daher ist es sehr wichtig, dass Sie ein solides Grundverständnis dafür erwerben. Für den Moment mag es genügen zu wissen, dass Ihre Main-Methode stets entweder static oder Shared sein muss.
Das Eingangstor zum Code Die Main-Methode ist auch als Einsprungspunkt zu einer Anwendung bekannt: Bei jedem Start Ihrer Anwendung wird die Main-Methode stets als erstes ausgeführt. Verfügt Ihr Programm über keine Main-Methode, wird es einfach nichts tun (man bekommt eine Fehler-
55
Windows Forms-Anwendungen erstellen
meldung wohl schon lange, bevor man es ausführen will). Die Main-Methode wird auch als letztes ausgeführt. Wenn Ihr Code mit der Ausführung fertig ist, kehrt er stets zu dieser Methode zurück. Der Main-Methode kommt also eine hohe Bedeutung zu. Entweder lässt sich der gesamte Programmcode in der Main-Methode unterbringen oder Main kann andere Methoden aufrufen. Von Main aus können Sie bestimmen, welches Formular anzuzeigen ist (viele Anwendungen können mehr als ein Windows Form-Objekt enthalten), Variablen festlegen, die von der restlichen Anwendung verwendet werden, oder beliebige weitere Vorbereitungsschritte ausführen. In unserer Main-Methode wird eine einzige Codezeile ausgeführt: Application.Run(New HelloWorld)
Diese Zeile ruft die Run-Methode des Application-Objekts (einen Teil des Namensraums System.Windows.Forms) auf. Die Run-Methode befiehlt dem Programm, in eine Nachrichtenschleife einzutreten. Das ist im Grunde die Art und Weise eines Programms, auf Eingaben vom Benutzer zu warten. Der Eintritt in eine Nachrichtenschleife ist vom Standpunkt des Benutzers aus fast immer der Startpunkt einer Anwendung. Morgen werden Sie mehr über Nachrichtenschleifen erfahren. Sie übergeben der Run-Methode eine Instanz Ihres Objekts HelloWorld. (Bitte beachten Sie, dass wir von einer Klasse zu einem Objekt übergegangen sind: Das New-Schlüsselwort erstellt eine Objektinstanz.) Das weist die Run-Methode an, Ihr Formular auf dem Bildschirm für den Dialog mit dem Benutzer anzuzeigen. Moment mal! Warum müssen wir eine Instanz der HelloWorld-Klasse an die Run-Methode übergeben? Dürfte das nicht zu einer Endlosschleife führen? Schließlich wird die MainMethode erneut ausgeführt, übergibt eine neue Instanz und so weiter. Nicht ganz. Um dies zu verstehen, müssen Sie zwischen einer Klasse und einer Anwendung unterscheiden. Wenn eine neue Instanz einer Klasse erzeugt wird, passiert nichts (es sei denn, man hätte einen Konstruktor erzeugt, aber so weit sind wir noch nicht). Wenn eine Anwendung startet, sucht sie zuerst nach einer Main-Methode, ganz gleich, wo sie diese findet oder ob sie sich in einer Klasse befindet, solange es sie nur gibt und sie in einer Klasse untergebracht ist. Main wird nur ein einziges Mal ausgeführt, wenn Ihre Anwendung gestartet wird, und danach nicht mehr. Anders ausgedrückt, wird Main bei jeder Anwendungsausführung gestartet, aber nicht immer dann, wenn eine Klasse erzeugt wird. Obwohl die Main-Methode ganz normal aussieht, führt jeder Versuch, sie von einem anderen Punkt in Ihrem Programm aus aufzurufen, zu einer Fehlermeldung, wie sie in Abbildung 2.3 zu sehen ist.
56
Ein weiterer Blick auf Ihre Anwendung
Abbildung 2.3: Eine Fehlermeldung erfolgt, wenn man versucht, die Main-Methode im Code auszuführen.
Obwohl nun Zeile 7 in Listing 2.1 Application.Run(New HelloWorld)
eine neue Instanz der Klasse HelloWorld erzeugt, wird die Main-Methode nicht erneut ausgeführt. Um die Trennung zwischen der HelloWorld-Klasse und der Main-Methode noch weiter zu unterstreichen, könnten Sie die Main-Methode in eine völlig andere Klasse stecken, und sie würde weiterhin funktionieren, wie Sie in Listing 2.3 sehen. Kurz und gut, Sie brauchen für eine Anwendung eine Main-Methode, die sich in einer Klasse befinden muss – doch diese Klasse können Sie willkürlich auswählen. Listing 2.3: Die Main-Methode lässt sich in jeder Klasse unterbringen 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14:
Imports System Imports System.Windows.Forms Public Class HelloWorld : Inherits Form Public Sub New() Me.Text = "HelloWorld!" End Sub End Class Public Class MyMainClass Public Shared Sub Main() Application.Run(New HelloWorld) End Sub End Class
Listing 2.3 ergibt wahrscheinlich etwas mehr Sinn, denn es zeigt ganz deutlich, dass die Main-Methode und die Windows Form-Klasse zwei völlig verschiedene Einheiten sind. Die Run-Methode in Zeile 12 erzeugt eine neue Instanz Ihrer Windows Form-Klasse (HelloWorld) und bewirkt, dass sie auf dem Bildschirm angezeigt wird.
57
Windows Forms-Anwendungen erstellen
Die Methode New (oder HelloWorld in C#) im Schlussteil von Listing 2.3 wird als Konstruktor bezeichnet. Das ist eine Methode, die ausgeführt wird, sobald eine neue Instanz einer Klasse erzeugt wird. Sie hat eine »Partner«-Methode namens Destruktor. Wir sprechen im nächsten Abschnitt über diese zwei Methoden.
Konstruktoren und Destruktoren Einen Konstruktor benutzt man normalerweise, um die Instanz zu initialisieren, erforderliche Variablen anzulegen und so weiter. In Visual Basic .NET hat ein Konstruktor stets den Namen New, wie die Zeilen 10 bis 12 in Listing 2.3 zeigen: 10: 11: 12:
Public Sub New() Me.Text = "HelloWorld!" End Sub
Das ist sinnvoll. Erinnern Sie sich an Zeile 7 aus Listing 2.1, wie Sie New klassenname verwendet haben: Application.Run(New HelloWorld)
Dieser Befehl ruft die New-Methode auf. In C# nimmt der Konstruktor denselben Namen wie die Klasse selbst an, so wie in den folgenden Zeilen aus Listing 2.2: Public HelloWorld() This Text = 'Hello World!" }
Die Me-Variable in Listing 2.1 11:
Me.Text = "Hello World!"
und die this-Variable in Listing 2.2 11:
thisText = "Hello World!";
sind spezielle Schlüsselwörter, die auf das Objekt verweisen, in dem sich der Code jeweils befindet. In diesem Fall verweisen sie auf das Windows Form-Objekt HelloWorld. Auf diese Weise lässt sich leicht auf das umschließende Objekt verweisen. Zeile 11 in Listing 2.1 und 2.2 weist die Zeichenfolge »Hello World!« der Eigenschaft Text in der HelloWorld-Klasse zu, welche, wie wir gesehen haben, die Titelzeile auf dem Windows Form festlegt. Umgekehrt wird ein Destruktor immer dann aufgerufen, wenn ein Objekt der Klasse zu entsorgen ist. In Visual Basic .NET wird dies bewerkstelligt, indem das Objekt mit nothing gleichgesetzt wird: objHelloWorld = new HelloWorld() objHelloWorld = nothing
58
Ein weiterer Blick auf Ihre Anwendung
In C# erfolgt dies durch Gleichsetzung der Klasse mit null: HelloWorld objHelloWorld = newHelloWorld(); objHelloWorld = null;
In VB .NET kann man jedoch keinen eigenen Destruktor erzeugen: Immer wenn Sie Ihr Objekt mit nothing gleichsetzen, übernimmt VB .NET die Entsorgung für Sie. C# erlaubt es, Ihren eigenen Destruktor zu bestimmen, der durch den gleichen Namen wie der Konstruktor, aber mit einer Tilde davor deklariert wird: ~HelloWorld() { 'etwas verarbeiten }
Konstruktoren erben In OOP gilt die Regel, dass der Konstruktor einer Klasse stets den Konstruktor seiner übergeordneten Klasse (Basisklasse) aufrufen muss. Beispielsweise erbt die Klasse HelloWorld von der Form-Klasse. Daher muss Ihre Klasse den Konstruktor der Form-Klasse aufrufen. Dieser wiederum muss den Konstruktor seiner übergeordneten Klasse aufrufen, nämlich den von ContainerControl, und so weiter, bis Sie die Wurzel des Stammbaums erreicht haben: Object. Werfen wir einen Blick darauf, warum dies notwendig ist. Sie erinnern sich, dass, sobald ein Objekt von einem anderen (seinem übergeordneten Objekt) erbt, es auch alle Eigenschaften und Methoden des anderen erbt. Manche davon können von bestimmten Variablen abhängen, die es zu initialisieren gilt. Stellen Sie sich beispielsweise eine Klasse vor, die eine Farbe-Eigenschaft erbt, die den Hintergrund des Formulars bestimmt. Dieser Eigenschaft muss ein echter Wert zugewiesen werden, bevor man sie verwenden kann, sonst erhält man eine Fehlermeldung (schließlich lässt sich die Hintergrundfarbe nicht auf »nichts« setzen). Die Farbe-Eigenschaft würde im Konstruktor Ihrer übergeordneten Klasse initialisiert werden. Daher müssen Sie den Konstruktor der Basisklasse aufrufen, damit die Farbe-Eigenschaft eine Bedeutung erhält. Diese Abhängigkeit von Basisklassen besagt zweierlei. Erstens hängen viele Klassen, die von anderen erben, von ihren Basisklassen ab, um so Informationen zu erhalten. Und zweitens bedeutet es, dass Sie in Ihrem Konstruktor weniger Initialisierungscode schreiben müssen, weil schon vieles in den Konstruktoren der Ihrer Klasse übergeordneten Klassen erledigt worden ist. Der Aufruf des Konstruktors der Basisklasse ist in Ihrem Konstruktor als allererstes zu erledigen. In VB .NET erfolgt dies durch den Aufruf der Methode MyBase.New, in C#, indem Sie Ihren Konstruktor zum Ausführen der Methode base() veranlassen: 'VB .NET Public Sub New() MyBase.New End Sub
59
Windows Forms-Anwendungen erstellen
'C# public HelloWorld(): base() { }
In all dem Code, den Sie heute geschrieben haben (Listings 2.1 bis 2.3), fehlen diese Aufrufe, doch warum erhalten wir dann keine Fehlermeldungen? Wenn Sie in Ihrer Klasse nur einen Konstruktor haben (mehr über multiple Konstruktoren im nächsten Abschnitt), ruft die CLR automatisch den Konstruktor der Basisklasse auf. Daher können Sie diese Zeilen weglassen.
Konstruktoren überladen Manchmal möchten Sie mehr als einen Konstruktor haben. Ein nützliches Beispiel dafür bestünde darin, dass Sie dem Benutzer erlauben möchten, auf mehr als eine Weise zu initialisieren: so etwa eine, die einen Wert für eine Variable erfordert, und eine, die keinen Wert erfordert. Sie können dies durch einen Vorgang namens Überladen erledigen. Zum besseren Verständnis dieses Konzepts schauen wir uns den Code an, wie er in Listing 2.4 abgedruckt ist. Listing 2.4: Konstruktoren in C# überladen 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16:
using System; using System.Windows.Forms; public class HelloWorld : Form { public static void Main() { Application.Run(new HelloWorld()); } public HelloWorld(): this("Hello World!") {} public HelloWorld(string strText): base() { thisText = strText; } }
Lassen Sie uns zunächst in Zeile 12 springen. Hier sehen wir einen Konstruktor, der einen string-Wert als Parameter übernimmt. Die Text-Eigenschaft des Formulars wird in Zeile 13 auf diesen Parameter eingestellt. Schließlich ruft dieser Konstruktor mit der base()-Methode den Konstruktor der Basisklasse auf. Soweit also nichts Neues.
60
Ein weiterer Blick auf Ihre Anwendung
In Zeile 10 stoßen wir auf etwas, das wie ein weiterer Konstruktor aussieht. Doch dieser übernimmt keine Parameter und verfügt über keinen Initialisierungscode. Schauen Sie aber mal auf den Code nach dem Doppelpunkt. Er ruft this auf, von dem wir wissen, dass es eine Referenz auf das aktuelle Objekt ist, und übergibt ihm die Zeichenfolge »Hello World!«. Was bewirkt dies? Zeile 10 besagt, dass im Fall des Aufrufs dieses Konstruktors statt dessen der Konstruktor in Zeile 12 aufgerufen und ihm als Parameter die Zeichenfolge »Hello World!« übergeben werden soll. Die Ausführung geht dann zu den Zeilen 12 und 13 über, die Text-Eigenschaft wird auf »Hello World!« gesetzt. Im Grunde sagen Sie also, dass der Parameter strText optional ist: Wenn kein Parameter angegeben ist, verwendet der Konstruktor die Standardzeichenfolge »Hello World!«. Zeile 7, die einen Konstruktor aufruft, könnte also entweder so Application.Run(new HelloWorld());
oder so Application.Run(new HelloWorld("I love NY"));
aussehen. Dies ruft den jeweils passenden Konstruktor auf, je nachdem, ob Parameter übergeben wurden. Der Konstruktor in Zeile 10 könnte sogar seine eigenen Initialisierungsroutinen ausführen und braucht den Konstruktor in Zeile 12 nicht aufzurufen, wenn man nicht will. In VB .NET sieht die Syntax etwas anders aus, wie man in Listing 2.5 sehen kann. Listing 2.5: Konstruktoren in VB .NET überladen 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18:
Imports System Imports System.Windows.Forms Public Class HelloWorld : Inherits Form Public Shared Sub Main() Application.Run(New HelloWorld) End Sub Public Sub New() Me.New("Hello World!") End Sub Public Sub New(strText as String) Me.Text = strText End Sub End Class
61
Windows Forms-Anwendungen erstellen
In Zeile 11 verwenden Sie das Schlüsselwort Me und rufen die New-Methode auf, wobei Sie eine Standardzeichenfolge bereitstellen. Die dem Listing 2.5 zugrunde liegenden Konzepte sind die gleichen wie in Listing 2.4. Beachten Sie, dass Sie keineswegs einen Konstruktor erzeugen müssen. Wenn Sie keinen erzeugen, dann erzeugt VB .NET (oder C#) einen für Sie. Dieser automatisch generierte Konstruktor ruft nur den Konstruktor der Basisklasse auf. Konstruktoren sind nicht die einzigen Methoden, die Sie überladen können. Doch in VB .NET müssen Sie das Schlüsselwort Overloads vor jede Methode stellen, die überladen wird (Konstruktoren natürlich ausgenommen), und zwar vor dem Schlüsselwort Public.
Ein abschließender Blick auf den Programmcode Nach der Untersuchung aller Codebestandteile lassen Sie uns rekapitulieren. Listing 2.1 ist erneut in Listing 2.6 zu sehen. Listing 2.6: Ihre erste Windows Forms-Anwendung in VB .NET 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14:
Imports System Imports System.Windows.Forms Public Class HelloWorld : Inherits Form Public Shared Sub Main() Application.Run(New HelloWorld) End Sub Public Sub New(strText as String) Me.Text = strText End Sub End Class
Die Zeilen 1 und 2 bestimmen die Namensräume, die in Ihre Anwendung importiert werden. Die Mitglieder dieser Namensräume stehen Ihrer Anwendung in Form ihrer Kurznamen (also Form anstelle von System.Windows.Forms.Form) zur Verfügung. Zeile 4 deklariert Ihre Klasse und die Tatsache, dass sie von der Form-Klasse erbt, der Basisklasse für alle Windows Forms-Anwendungen. Zeile 6 startet die MainMethode, den Einsprungspunkt Ihrer Anwendung. Sie ruft die Run-Methode
62
Das Kompilieren von Windows Forms
auf, welche das Formular zur Darstellung auf dem Bildschirm und zum Warten auf Benutzereingaben veranlasst. Sie übergeben eine neue Instanz Ihrer Klasse, um ihr mitzuteilen, welches Formular angezeigt werden soll. Die Zeilen 10 bis 12 bilden einen Konstruktor für die HelloWorld-Klasse. In Zeile 11 weisen Sie lediglich der Text-Eigenschaft in Ihrem Formular die Zeichenfolge »Hello World!« zu. Das ändert die Titelzeile Ihrer Anwendung. Sobald Sie einmal die Grundlagen verstanden haben, können Sie erkennen, wie einfach dieser Quellcode war. Jetzt kennen Sie alle Komponenten, die für die Erstellung von Windows Forms notwendig sind, und können anfangen, Ihre eigene Windows Forms-Anwendung zu erstellen!
2.2
Das Kompilieren von Windows Forms
Der nächste Schritt bei der Anwendungserstellung besteht im Kompilieren Ihres Quellcodes zu MSIL. Die Vorgehensweise variiert je nach der verwendeten Programmiersprache, doch sie ist stets recht ähnlich. Die einfachste Kompiliermethode besteht in der Verwendung von Visual Studio .NET. VS .NET ist eine integrierte Entwicklungsumgebung (IDE), die Ihnen die Erstellung beliebiger Anwendungstypen an einem zentralen Ort erlaubt, ganz gleich welche Programmiersprache Sie benutzen. Lassen Sie uns einen kleinen Rundgang durch diese Entwicklungsumgebung machen und betrachten, wie man hier Code schreiben und kompilieren kann. Öffnen Sie VS .NET und starten ein neues Projekt, indem Sie NEU/PROJEKT aus dem DATEI-Menü auswählen. Das Dialogfeld NEUES PROJEKT (Abbildung 2.4) erscheint.
Abbildung 2.4: Das Dialogfeld NEUES PROJEKT stellt Ihnen eine VB .NET- oder C#-Anwendung zur Auswahl.
63
Windows Forms-Anwendungen erstellen
Wählen Sie mit Hilfe dieses Dialogs eine Anzahl verschiedener Optionen sowohl für C# als auch für Visual Basic .NET. Klicken Sie in der linken Fensterhälfte auf VISUAL BASICPROJEKTE und in der rechten auf WINDOWS-ANWENDUNG. Wählen Sie einen passenden Namen und einen Speicherplatz für dieses Projekt und klicken Sie auf OK. Sobald VS .NET mit der Verarbeitung fertig ist, zeigt es ein neues Projekt im PROJEKTMAP(obere rechte Ecke der VS .NET-Anwendung) an, mit diversen Dateien darunter (siehe Abbildung 2.5).
PEN-EXPLORER
Zunächst finden Sie einen Ordner namens VERWEISE, der alle Namensräume anzeigt, die Ihre Anwendung verwenden wird; beachten Sie bitte, dass sich innerhalb des Ordners (auf das Plus-Zeichen klicken, um ihn aufzuklappen) bereits einige Namensräume befinden, die für Sie vorgegeben sind. Wenn Sie mit den Statements using oder Imports einen Namensraum haben wollen, der sich nicht hier befindet, müssen Sie den Namensraum hinzufügen, indem Sie mit der rechten Maustaste auf den Ordner klicken und VERWEIS HINZUFÜGEN auswählen. Daraufhin erscheint ein Dialogfenster, das Ihnen bei der Auswahl einer Assembly hilft. Die nächste ist die Datei AssemblyInfo.vb, die verwendet wird, um Informationen über Ihre Assembly bereitzustellen. Schließlich findet sich noch eine Datei namens Form1.vb, worin Ihr Code abgelegt wird. Öffnen Sie diese Datei mit einem rechten Mausklick und der Auswahl CODE ANZEIGEN. Ihr Fenster sollte nun ähnlich aussehen wie in Abbildung 2.5.
Abbildung 2.5: Ihre leere Windows Forms-Anwendung wird im Projektmappen-Explorer angezeigt.
In VS .NET wird ein einfach zu bedienender Quellcode-Editor mitgeliefert. Er gruppiert ähnliche Dinge wie etwa Klassen, und er erlaubt Ihnen, sie auszublenden (indem Sie auf das Minus-Symbol links daneben klicken), um so die Arbeitsumgebung übersichtlicher zu
64
Das Kompilieren von Windows Forms
machen. Beachten Sie auch, dass VS .NET etwas Code für Sie erzeugt hat, den Sie in dem Code-Bereich im Windows Forms-Designer finden. Diesen Code werden wir hier nicht besprechen, aber wenn Sie mal durchblättern, dann werden Sie zumindest eine Methode wiedererkennen: den Konstruktor der Klasse. Mit dem restlichen Code integriert VS .NET Ihre Anwendung in die Entwicklungsumgebung. (Das soll nicht bedeuten, dass Sie den Code nicht löschen könnten, wenn Sie wollen.) Sie können nun Ihren Code an beliebiger Stelle zwischen den Zeilen Public Class und End Class einfügen. Sie können auch den Namen der Klasse bearbeiten. Das Eigenschaftenfenster unten rechts gestattet Ihnen die Änderung des gespeicherten Dateinamens, des Autorennamens und anderer Attribute. Sobald Sie den Code geschrieben haben, wählen Sie im Menü ERSTELLEN den Befehl ERSTELLEN, um Ihre Anwendung zu erzeugen. Befinden sich Fehler im Code, werden sie in einem Ausgabe-Fenster angezeigt. Läuft alles erfolgreich ab, dann erzeugt VS .NET eine .EXE-Datei (der Dateiname hängt vom gewählten Projektnamen ab), die Sie wie jede beliebige Anwendung ausführen können. VS .NET enthält zahlreiche Funktionen, die hier nicht vorgestellt werden. Erforschen Sie die Entwicklungsumgebung weiter und schreiben Sie Ihren Code. Wenn Sie lieber nicht mit VS .NET arbeiten wollen oder keinen Zugang dazu haben, können Sie Ihren Code mit dem jeweils nötigen Befehlszeilen-Compiler kompilieren. Das ist ein einfaches Programm, das einige Parameter übernimmt (darunter Ihre QuellcodeDatei) und eine kompilierte EXE-Datei »ausspuckt«. Der nötige Compilerbefehl ist entweder vbc.exe (für Quellcode in Visual Basic .NET) oder csc.exe (für Quellcode in C#). Für beide Methoden ist die Syntax dieselbe, daher betrachten wir hier nur die erste: vbc optionen quellcodedatei
In seiner einfachsten Form sieht Ihr Befehl wie folgt aus: vbc helloworld.vb
Tabelle 2.2 führt die gebräuchlichen Befehlszeilenoptionen auf, die Sie nutzen können. Geben Sie vbc /? oder csc /? In der Befehlszeile ein, um alle Optionen anzusehen. Option
Beschreibung
@
Spezifiziert eine Textdatei, die die für den Befehl zu verwendenden Optionen enthält. Sie müssen einen Dateinamen angeben: @dateiname.
/bugreport
Erzeugt eine Fehlerberichtsdatei. Sie müssen einen Dateinamen angeben: /bugreport:dateiname.
Tabelle 2.2: Gebräuchliche Befehlszeilen-Optionen für den Compiler
65
Windows Forms-Anwendungen erstellen
Option
Beschreibung
/debug
Gibt diverse Informationen aus. Verwenden Sie: 왘 /debug+ um Informationen auszugeben 왘
/debug- um keine Informationen auszugeben
왘
/debug:full um alle Debugging-Informationen auszugeben (Voreinstellung)
왘
/debug:pdbonly um lediglich die pdb-Symboldatei zu erstellen
/main
Gibt die Klasse an, die die Main-Methode enthält. Verwenden Sie /m als Kurzform.
/nologo
Unterdrückt das Microsoft-Logo.
/optioncompare
Verwenden Sie binary oder text, um anzugeben, wie alle Zeichenfolgenvergleiche in Ihrem Code durchgeführt werden sollen.
/optionexplicit
Erfordert die explizite Deklaration aller Variablen vor dem Einsatz. Verwenden Sie /optionexplicit+ oder /optionexplicit-.
/optionstrict
Erzwingt strikte Befolgung der Sprachsemantik. Verwenden Sie /optionstrict+ oder /optionstrict-.
/out
Gibt die Ausgabestandort und den Namen Ihrer kompilierten MSIL-Datei an.
/reference
Gibt zu referenzierende Assemblies in Ihrer Anwendung an (jene, die durch die Schlüsselwörter using oder Imports spezifiziert wurden). Verwenden Sie /r als Kurzform.
/target
Gibt den Typ der zu generierenden Ausgabedatei an. Verwenden Sie /t als Kurzform. Setzen Sie ein: 왘 /t:exe für eine ausführbare Konsolen-Anwendung; 왘
/t:library für eine DLL;
왘
/t:winexe für eine ausführbare Windows-Anwendung;
왘
/t:modul für ein Modul.
/win32icon
Gibt das zu verwendende Symbol (.ico-Datei) für das Anwendungssymbol an.
/win32resource
Gibt die zu verwendenden Ressourcen-Dateien (.res-Dateien) an.
Tabelle 2.2: Gebräuchliche Befehlszeilen-Optionen für den Compiler (Forts.)
Für unsere Anwendung verwenden wir normalerweise nur zwei Optionen: /target (Kurzform /t) und /reference (Kurzform /r). Um beispielsweise die Anwendung HelloWorld zu kompilieren, sollte das Ziel winexe sein, und wir müssen Referenzen auf die Assemblies hinzufügen, die gebraucht werden (je nach den Namensräumen, die wir importiert
66
Eine vollständige Anwendung
haben). Wenn wir auf unseren Quellcode und die Tabelle 2.1 zurückblicken, sehen Sie, dass wir die Namensräume System und System.Windows.Forms verwenden. Sie befinden sich jeweils in den Assemblies system.dll und system.windows.forms.dll. Daher sieht unsere Befehlszeile schließlich so aus: vbc /t:winexe /r:system.dll /r:system.windows.forms.dll HelloWorld.vb
Der Compiler erlaubt es Ihnen auch, mehrere Referenzen in einer einzigen Liste zu kombinieren. Ein Beispiel: vbc /t:winexe /r:system.dll,system.windows.forms.dll HelloWorld.vb
Die Assembly mscorlib.dll aus Tabelle 2.1 steht automatisch allen Anwendungen zur Verfügung: Sie ist die grundlegende Assembly für .NET. Aus diesem Grund brauchen Sie keinen expliziten Verweis dafür einzufügen. Von Ihrer Eingabeaufforderung aus veranlasst würde der Befehl die in Abbildung 2.6 sichtbare Ausgabe erzeugen.
Abbildung 2.6: Eine erfolgreiche Kompilierung führt zu diesem Ergebnis.
Welchen Weg Sie auch wählen, die Kompilierung ist ein notwendiger Schritt zu Ihrer Windows Forms-Anwendung. Im restlichen Buch verwenden wir die Befehlszeilenmethode, falls nicht anders festgelegt. Anhand der Befehle können Sie leicht bestimmen, welche Referenzen Sie Ihrem VS .NET-Projekt hinzufügen müssen.
2.3
Eine vollständige Anwendung
Da Sie nun mit allen Bestandteilen einer Windows Forms-Anwendung ausgestattet sind, können Sie mit dem Aufbau einer Applikation beginnen, die etwas nützlicher als das »Hello World«-Programm ist. Im folgenden Abschnitt werden wir einen einfachen Taschenrechner erstellen, den Sie dann an Tag 5 und 6 erweitern.
67
Windows Forms-Anwendungen erstellen
Um einen Taschenrechner zu erstellen, brauchen Sie mindestens drei Dinge (neben Windows Forms): 쐽
eine Stelle, an der der Benutzer eine Zahl bzw. Zahlen eingeben kann,
쐽
eine Stelle, an der er das Ergebnis sehen kann (möglicherweise an der gleichen Stelle),
쐽
eine oder mehrere Schaltflächen, die es ihm erlauben, mathematische Operationen auszuführen. Der Einfachheit halber beschränken wir uns hierbei auf die Addition.
Diese Komponenten werden von Windows Forms-Steuerelementen (Controls) bereitgestellt: interaktiven Elementen der Benutzeroberfläche (englisch User Interface, abgekürzt UI). Ab Tag 4 werden wir diese Steuerelemente genauer unter die Lupe nehmen, so dass wir uns hier nicht allzu lange damit aufhalten müssen. Zunächst wollen wir einmal den grundlegenden Rahmen für Ihre Anwendung erstellen. Dazu gehören alle Bestandteile, über die Sie heute etwas erfahren haben. Werfen Sie einen Blick auf Listing 2.7 (der komplette C#-Code wird später gezeigt). Listing 2.7: Das Fundament für die Taschenrechner-Anwendung 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18:
Imports System Imports System.Windows.Forms Namespace TYWinforms.Day2 Public Class Calculator Public Shared Sub Main() Application.Run(New Calculator) End Sub Public Sub New() End Sub End Class End Namespace
Speichern Sie diesen Code unter dem Dateinamen Listing 2.7.vb. Im Listing gibt es nichts Neues zu entdecken. Es ist sogar fast identisch mit Listing 2.1, nur dass in Zeile 4 eine Namensraum-Deklaration auftaucht. Diese teilt der CLR mit, dass diese Klasse zum Namensraum TYWinforms.Day2 gehört.
68
Eine vollständige Anwendung
Die Calculator-Klasse wird in Zeile 6 deklariert, sie erbt von der Klasse System.Windows.Forms.Form. Die Zeilen 8 bis 10 enthalten die Main-Methode, die einfach die Run-Methode aufruft und eine neue Instanz der aktuellen Klasse als Parameter übergibt. Die Zeilen 12 bis 14 enthalten eine Konstruktor-Methode, die momentan noch leer ist – in Kürze fügen wir den entsprechenden Code hinzu. In dieser Anwendung benötigen wir vier Windows Forms-Steuerelemente: zwei Textfelder (TextBox) für die Zahleneingabe, ein Bezeichnungsfeld (Label) für die Ergebnisanzeige und eine Schaltfläche (Button) für die Erledigung der Addition. Alle diese Steuerelemente gehören dem Namensraum System.Windows.Forms an, so dass Sie sie genau wie die FormKlasse verwenden können. Geben Sie in Zeile 7 Ihrer Listing 2.7.vb-Datei folgenden Code ein: Private Private Private Private
WithEvents btnAdd as Button tNumber1 as TextBox tNumber2 as TextBox lblAnswer as Label
Dieser Abschnitt deklariert vier Variablen als Steuerelemente. Das Schlüsselwort WithEvents bedeutet im Grunde, dass ein bestimmtes Steuerelement Aktionen ausführen kann. Listing 2.8 zeigt die vollständige Konstruktor-Methode. Listing 2.8: Ihre Anwendung initialisieren 12: 13: 12: 13: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27:
Public Sub New() Me.btnAddYour Application Public Sub New() Me.btnAddInitializing Your Application Public Sub New() Me.btnAdd = New Button Me.tbNumber1 = New TextBox Me.tbNumber2 = New TextBox Me.lblAnswer = New Label tbNumber1.Location = New Point(0,0) tbNumber2.Location = New Point(100,0) btnAdd.Location = New Point(0,25) btnAdd.Text = "addieren" AddHandler btnAdd.Click, new EventHandler(AddressOf Add) lblAnswer.Location = New Point(0,75) Me.Controls.Add(btnAdd)
69
Windows Forms-Anwendungen erstellen
28: 29: 30: 31:
Me.Controls.Add(tbNumber1) Me.Controls.Add(tbNumber2) Me.Controls.Add(lblAnswer) End Sub
Fügen Sie diesen Code Ihrer Datei Listing 2.7.vb hinzu. Wir gehen auch hier noch nicht auf die Spezifika der einzelnen Steuerelemente ein, sondern kümmern uns nur um die Grundlagen. Die Zeilen 13 bis 16 erzeugen neue Instanzen jedes Steuerelements. Sie fragen sich vielleicht, warum die Zeilen 13 bis 16 nötig sind, wo wir doch die Variablen schon zuvor deklariert haben. Der Grund: Im vorhergehenden Codestück erzeugten wir Variablen, gaben ihnen Namen und teilten der CLR mit, um welche Variablentypen es sich handelt. Wir haben jedoch diesen Variablen keinerlei Werte zugewiesen. Das erledigen die Zeilen 13 bis 16: Sie weisen den Variablen Objekte zu (wie durch die Gleichheitszeichen angezeigt wird). Das Erzeugen eines Objekts – sei es ein Steuerelement, ein Formular oder ein anderes Objekt – ist ein zweistufiger Vorgang: Deklaration der Variablen und Zuweisung eines Wertes. Es ist jedoch möglich, beide Schritte in einer einzigen Zeile auszuführen: Dim btnAdd as Button = New Button
Oder sogar noch einfacher: Dim btnAdd as New Button
Wir werden ab jetzt diese Kurzform verwenden. Die Zeilen 18, 19, 21 und 25 machen alle das Gleiche: Sie legen die Positionen (»Locations«) für die Steuerelemente in unserem Windows Form fest. Die Eigenschaft Location übernimmt einen einzelnen Parameter des Typs Point, der die Stelle beschreibt, wo das Steuerelement platziert werden soll. Der Konstruktor für Point übernimmt wiederum zwei Parameter (eine x- und eine y-Koordinate). Man kann zwar Steuerelemente übereinander legen, aber das ist meist nicht sonderlich nützlich. Wir gehen morgen auf dieses Thema ein. Sie sollten bereits wissen, was Zeile 22 bewirkt: Sie setzt die Text-Eigenschaft der Schaltfläche auf den Wert »Addieren«. Erinnern Sie sich, dass die Text-Eigenschaft des Formulars eine Titelzeile festlegt. Die gleiche Eigenschaft für einen Button legt den Text fest, der darauf angezeigt wird. Zeile 23 bringt etwas Neues. Was sie genau bedeutet, erfahren wir an Tag 5. Für den Augenblick genügt es zu wissen, dass diese Zeile der CLR mitteilt, dass die Add-Methode (siehe Listing 2.9) ausgeführt werden soll, wenn diese Schaltfläche angeklickt wird.
70
Eine vollständige Anwendung
Die Zeilen 27-30 schließlich fügen Ihrem Formular die einzelnen Steuerelemente hinzu. Dieser Schritt ist notwendig, sonst würde die CLR nicht wissen, wo sie die Steuerelemente anzeigen soll. Sie würden nur im Speicher existieren und wertvolle Ressourcen belegen. Listing 2.9 zeigt Ihnen den Schlussteil unseres Codes: die Add-Methode. Fügen Sie diesen Code unter Zeile 31 ein, jedoch vor dem Ende der Klasse. Listing 2.9: Die Zahlen addieren 33: 34: 35:
Public Sub Add(ByVal Sender as Object, ByVal e as EventArgs) lblAnswer.Text = CStr(CInt(tbNumber1.Text) + CInt(tbNumber2.Text)) End Sub
Diese Methode nimmt zwei Parameter entgegen, die wir aber für den Augenblick ignorieren, da sie für diese einfache Anwendung nicht von großem Nutzen sind. Zeile 34 übernimmt die Werte der zwei Textfelder (unter Nutzung der jeweiligen Text-Eigenschaft), addiert sie und zeigt sie im Bezeichnungsfeld (Label) mit Hilfe von dessen Text-Eigenschaft an. Die Funktionen CStr und CInt wandeln die Benutzereingaben in Zeichenfolgen bzw. Ganzzahlen um. Das ist notwendig, weil die CLR nicht weiß, wie sie Benutzereingabewerte interpretieren soll. Beispielsweise könnte das Zeichen 9 als Zahl oder Zeichenfolge interpretiert werden, also müssen wir einen expliziten Datentyp dafür bereitstellen. Zeile 34 führt die folgenden Arbeitsschritte aus: 1. holt die Werte aus den zwei Textfeldern, 2. wandelt die Werte mit Hilfe von CInt in Integer um, 3. addiert die zwei Integer, 4. wandelt das Ergebnis in eine Zeichenfolge um, die im Bezeichnungsfeld angezeigt wird (dieser Schritt ist erforderlich, weil die Text-Eigenschaft stets eine Zeichenfolge erwartet und sich gegenüber einer Ganzzahl verweigert). Das war's auch schon. Speichern Sie den zusammengefügten Code als calculator.vb in Ihrem Verzeichnis c:\winforms\day2 und kompilieren Sie ihn unter Verwendung von VS .NET oder eines Befehlszeilen-Compilers: vbc /t:winexe /r:system.dll /r:system.windows.forms.dll /r:system.drawing.dll Calculator.vb
Dieser Befehl verwendet den Compiler von VB .NET, liefert eine ausführbare WindowsDatei und referenziert die Assemblies system.dll, system.windows.forms.dll sowie system.drawing.dll (diese wurde mit Hilfe der Tabelle 2.1 bestimmt), um die Anwendung zu kompilieren. Abbildung 2.7 zeigt eine Beispielausgabe.
71
Windows Forms-Anwendungen erstellen
Listing 2.10 zeigt den gleichen Quellcode, nur eben in C#.
Abbildung 2.7: Die Anwendung addiert erfolgreich zwei Zahlen.
Listing 2.10: Ein C#-Taschenrechner 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26:
72
using System; using System.Windows.Forms; using System.Drawing; namespace TYWinforms.Day2 { public class Calculator : Form { private Button btnAdd; private TextBox tbNumber1; pribate TextBox tbNumber2; private Label lblAnswer; public static void Main() { Application.Run(new Calculator()); } public Calculator() { this.btnAdd = new Button(); this.tbNumber1 = new TextBox(); this.tbNumber2 = new TextBox(); this.lblAnswer = new Label(); tbNumber1.Location = new Point(0,0); tbNumber2.Location = new Point(100,0); btnAdd.Location = new Point(0,25);
Eine vollständige Anwendung
27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43:
btnAdd.Text = "Addieren"; btnAdd.Click += new EventHandler(this.Add); lblAnswer.Location = new Point(0,75); this.Controls.Add(btnAdd); this.Controls.Add(tbNumber1); this.Controls.Add(tbNumber2); this.Controls.Add(lblAnswer); } public void Add(object Sender, EventArgs e) { lblAnswer.Text = Convert.ToString(Convert.ToInt32 (tbNumber1.Text) + Convert.ToInt32(tbNumber2.Text)); } } }
Neben ein paar semantischen Unterschieden zum VB .NET-Code sollten Sie auch über andere Dinge Bescheid wissen. Alle Zeilen sind bis zur Zeile 28 miteinander identisch. Beachten Sie, dass Sie die AddHandler-Methode nicht mehr verwenden und das Schlüsselwort WithEvents aus Zeile 8 verschwunden ist. C# setzt eine andere Methode ein, um Objekte Aktionen ausführen zu lassen. Darauf kommen wir an Tag 5 zu sprechen. Andere Unterschiede treten nur noch in den Zeilen 39 und 40 auf. Statt die Funktionen CStr und CInt für die Datentypumwandlung zu verwenden, nutzen Sie nun die Methoden Convert.ToString und Convert.ToInteger. Wenn Sie mit C# vertraut sind, fragen Sie sich vielleicht, warum wir nicht den Casting-Operator für die Konvertierung von Datentypen verwendet haben, beispielsweise (string) und (int) vor den jeweiligen Variablennamen: (int)tbNumber1.Text usw. Der Grund liegt in dem Umstand, dass man in C# weder implizit noch explizit einen string in einen anderen Basisdatentyp konvertieren kann. Die Klasse System.Convert verfügt hingegen über alle nötigen Methoden, um jeden beliebigen vordefinierten Datentyp in einen anderen umzuwandeln (ToInt32, ToString, ToDouble usw.). Der Befehl zum Kompilieren ist der gleiche, nur dass Sie jetzt den C#-Compiler nutzen: csc /t:winexe /r:system.dll /r:system.windows.forms.dll /r:system.drawing.dll Calculator.cs
73
Windows Forms-Anwendungen erstellen
2.4
Zusammenfassung
Die heutige Lektion war sehr intensiv. Durch das Untersuchen eines einfachen Quellcodes haben Sie eine ganze Menge über das .NET Framework und seine Arbeitsweise gelernt, u.a. über Klassen, Namensräume, Assemblies und Methoden. Klassen sind Baupläne für Objekte. In einer Klasse definieren Sie die Eigenschaften, Methoden und Ereignisse, die ein Objektbenutzer verwenden kann. Diese offen gelegten – also für die Allgemeinheit bereitgestellten – Elemente werden mit Hilfe des Schlüsselworts public deklariert. Elemente, die Sie nicht offen legen möchten, erfordern das Schlüsselwort private. Sie haben auch etwas über Klassenvererbung gelernt. Eine Klasse kann von einer anderen erben, um so deren Funktionsumfang zu nutzen – das erspart Ihnen viel Codeschreiben. Alle Ihre Windows Forms-Klassen erben von der Klasse System.Windows.Forms.Form. Klassen verfügen über Konstruktoren und Destruktoren, also über Methoden, die die von einer Klasse verwendeten Ressourcen initialisieren und wieder freigeben. In C# hat der Konstruktor den gleichen Namen wie die Klasse; der Destruktor erhält den gleichen Namen, allerdings mit einer vorangestellten Tilde. VB .NET hingegen verfügt über keine Destruktoren und der Konstruktor trägt immer den Namen New. Namensräume sind Gruppierungen von Klassen, und Assemblies sind physische Dateien, die Namensräume gruppieren. Namen von Assemblies enden auf .dll und lassen sich von Anwendungen mit Hilfe der Option /r des Compilers referenzieren. Die Main-Methode ist stets der Anfang einer Anwendung. Sie muss sich in einer Klasse befinden, aber es ist gleichgültig, in welcher. In der Regel ruft diese Methode die Methode Application.Run auf, welche ein Formular am Bildschirm anzeigt und auf Benutzereingaben wartet. Zum Schluss haben Sie gelernt, wie man alles zusammenfügt, indem Sie eine funktionierende Windows Forms-Anwendung erstellten. Mit diesem Wissen können Sie nun beinahe jeden Bestandteil einer Windows Forms-Anwendung identifizieren und Ihre eigene Anwendung erstellen.
2.5 F
Bedeutet der Import von Namensräumen zusätzlichen Verwaltungsaufwand und somit Speicherbedarf für meine Anwendung? A
74
Fragen und Antworten
Nicht unbedingt. Die Objekte in importierten Namensräumen werden nur bei Bedarf geladen und nicht alle auf einmal. Daher benötigt die Verwendung externer Objekte zwar schon etwas (Speicher-) Ressourcen, aber nur in recht geringem Umfang.
Workshop
2.6
Workshop
Dieser Workshop soll Ihnen helfen, sich besser an die heute behandelten Begriffe zu erinnern. Es hilft Ihnen sehr, die Antworten vollständig zu verstehen, bevor Sie weiterlesen. Die Antworten zu den Quizfragen und den Übungen finden Sie in Anhang A.
Quiz 1. Wie heißt die Basisklasse, von der alle anderen Klassen erben? 2. Was bewirken die Schlüsselwörter shared und static? 3. Wahr oder falsch? Man kann vorhandene Namensräume um eigene Klassen erweitern. 4. Wahr oder falsch? Die Main-Methode wird jedes Mal ausgeführt, wenn eine neue Instanz ihrer Klasse erzeugt wird. 5. Aus welchem Grund ist der folgende Code nicht ausreichend? Was muss außerdem verwendet werden? dim MyObject as Button
6. Was stimmt mit dem folgenden Kompilierungsbefehl nicht? (Tipp: Er kann mehr als einen Fehler enthalten.) csc /type:windowsexe /r:system.dll /r:system.drawing.dll / r:system.drawing.text.dll dateiname.vb
7. Was bedeuten die folgenden Eigenschaften? Button.CanSelect Button.Cursor Form.AllowDrop Form.Visible
8. Nennen Sie drei semantische Unterschiede zwischen C# und Visual Basic .NET. 9. Wahr oder falsch? Jede Klasse muss einen Konstruktor haben.
Übung Erweitern Sie das heutige Taschenrechner-Beispiel. Fügen Sie weitere Schaltflächen hinzu, mit denen sich arithmetische Operationen ausführen lassen: Subtraktion, Multiplikation und Division. Versuchen Sie, die Anwendung sowohl in C# als auch in Visual Basic .NET zu erstellen. Verschönern Sie die Benutzeroberfläche Ihres Taschenrechners durch den großzügigen Gebrauch von Bezeichnungsfeldern (Label-Steuerelementen).
75
Mit Windows Forms arbeiten
3
Mit Windows Forms arbeiten
An den ersten zwei Tagen haben Sie eine ganze Menge über die Funktionsweise von Windows Forms-Anwendungen gelernt. Sie können jeden ihrer Bestandteile identifizieren und beschreiben, wissen, wie er mit .NET zusammenarbeitet, und Sie können auf die Verwendung derjenigen Teile schließen, die Ihnen noch unbekannt sind. Da Sie nun über das Grundlagenwissen verfügen, um Windows Forms zu erstellen, ist es an der Zeit, mehr ins Detail zu gehen. Die heutige Lektion konzentriert sich auf alle Aspekte des Objekts System.Windows.Forms.Form und zeigt Ihnen die Vielfalt an Möglichkeiten, mit denen Sie Ihre Anwendungen anpassen können. Heute lernen Sie, 쐽
wie man das Object-Objekt verwendet,
쐽
in welcher Weise Sie Layout und Aussehen eines Formulars steuern,
쐽
wie man Interaktion in Formularen kontrolliert,
쐽
was die Nachrichtenschleife ist und wie sie mit der CLR zusammenarbeitet,
쐽
wie Sie mit Tastatur- und Mauseingaben umgehen,
쐽
auf welche Weise man Formulare veranlasst, mit Drag & Drop zu arbeiten.
3.1
Das objektorientierte Windows-Formular
Nun sollten Sie langsam anfangen, sich Ihre Formulare als Objekte vorzustellen. Stellen Sie sich das Windows -Formular auf Ihrem Bildschirm als ein echtes Fenster in Ihrem Haus vor. Sie können die Farbe des Fensters (bzw. seines Rahmens) und seine Transparenz ändern, den Typ der Handgriffe und des Glases sowie die Vorhänge usw. Sie können praktisch alles, was Ihnen einfällt, mit Ihrem Fenster anstellen (vorausgesetzt, Sie verfügen über die entsprechenden Mittel). Das Gleiche gilt auch für Windows Forms. Aus der gestrigen Lektion wissen Sie, dass Sie die Titelzeile und die Größe eines Formulars ändern können. Das trifft auch auf die Titelleiste zu wie auch auf die Anordnung der Objekte im Formular, die Farbe, Transparenz und Sichtbarkeit, die Umrisslinien, die Schaltflächen usw. In Abbildung 3.1 sehen Sie nur ein paar der Layouts, die Ihr Formular annehmen kann. Daher ist es hilfreich, sich Windows Forms als generische Objekte vorzustellen, die sich in jeder gewünschten Weise bearbeiten und anpassen lassen. Sie können quasi in den »Laden« der .NET-Klassenbibliothek gehen und sich ein Form-Objekt heraussuchen. Dann schneiden Sie es sich nach Ihren Wünschen zu.
78
Das objektorientierte Windows-Formular
Abbildung 3.1: Formulare können transparent, rahmenlos und skalierbar sein.
Mit dem Objekt Object arbeiten An den Tagen 1 und 2 haben Sie mit ein paar Objekten gearbeitet, aber Sie haben noch nicht das Objekt Object untersucht, die Basisklasse aller .NET-Klassen einschließlich der Windows Forms-Formulare. Da es von jeder Klasse geerbt wird, werden Sie von jetzt ab damit umgehen, und so ist es ratsam, ein wenig mehr darüber zu erfahren, insbesondere über die Methoden, die es Ihnen zur Verfügung stellt. Object ist die allgemeinste Klasse, die Sie in .NET verwenden können. Daher wird sie verwendet, um jedes ansonsten unbekannte Objekt darzustellen. Wenn Sie beispielsweise folgendes Statement zum Erzeugen einer Variablen verwenden, weiß die CLR nicht, als welchen Typ sie sie erzeugen soll, und erzeugt lediglich ein Object: Dim MyObjectVariable
Weil Ihre Variable nur ein Objekt ist, verlieren Sie eine Menge Funktionalität, die Sie durch das Deklarieren eines expliziten Datentyps gewinnen würden. So könnten Sie etwa einem Integer einen weiteren Integer hinzufügen, aber Sie können keine Objects aneinander hängen; ungeachtet Ihrer guten Absicht würde dies zu einer Fehlermeldung führen. Daher empfiehlt es sich, stets einen spezifischen Typ anzugeben, statt ein Object als Mädchen für alles zu verwenden. Standardmäßig müssen Ihre Anwendungen stets einen Typ für Ihre Variablen deklarieren. Das verhindert den falschen Einsatz der Variablen und verbessert das Leistungsverhalten. Sie können aber diese Sicherheitsfunktion durch Deaktivieren des Merkmals Option Strict abschalten. Um Näheres darüber zu erfahren, lesen Sie bitte in Tag 2 den Abschnitt »Das Kompilieren von Windows Forms«.
79
Mit Windows Forms arbeiten
Sie werden in der Windows Forms-Programmierung viele Funktionen kennen lernen, die für Sie Objekte erzeugen. So ruft etwa die Methode ConfigurationSettings.GetConfig Konfigurationseinstellungen aus einer Datei ab. Die Funktion liefert einen Datentyp Object, weil sie nicht genau weiß, was Sie mit den zurückgegebenen Ergebnissen machen werden. Wird aber ein Object geliefert, können Sie die Ergebnisse durch Typumwandlung (Casting) in jeden gewünschten Datentyp transformieren. In C# wandeln Sie den Typ einer Variablen folgendermaßen um: myDataType myVariable; // Variable vom Typ myDataType deklarieren myVariable = (myDataType) ConfigurationSettings.GetConfig ("some setting"); // Einstellungen abrufen und in myDataType umwandeln
Das Objekt Object verfügt über fünf Hauptmethoden: Equals, ReferenceEquals, GetHashCode, GetType und ToString. Sie sind allesamt nützlich, daher wollen wir sie im Detail betrachten. Denken Sie daran, dass jedes Objekt von Object erbt, so dass diese Methoden jedem Objekt, das Sie verwenden, zur Verfügung stehen. Die Equals-Methode ist sowohl ein statisches als auch ein nicht-statisches Element. Sie können sie daher sowohl von einer bestimmten Objektinstanz aus als auch vom Typ Object selbst aus aufrufen. Listing 3.1 zeigt beispielsweise ein Codefragment, in dem sowohl statische als auch nicht-statische Versionen der Equals-Methode verwendet werden. Listing 3.1: Der Gebrauch der Equals-Methode 1: 2: 3: 4: 5: 6: 7: 8:
int intA; int intB; intA = 4; intB = 5; Console.WriteLine("{0}", intA.Equals(intB)); // nicht-statische Methode Console.WriteLine("{0}", ObjectEquals(intA, intB)); // statische Methode
Die Zeilen 7 und 9 geben beide »false« in der Befehlszeile aus, weil die Objekte intA und intB nicht gleich sind (intA ist 4, und intB ist 5). Zeile 7 ruft jedoch die Equals-Methode von einer Instanz aus auf, während Zeile 9 Equals von der Object-Klasse selbst aus aufruft. Sehr häufig werden Sie zwei Objekte miteinander vergleichen müssen, so dass sich diese Methode als nützlich erweisen dürfte. Die Console.WriteLine-Methode gibt eine Zeichenfolge in der Befehlszeile aus. Der erste Parameter besteht in der auszugebenden Zeichenfolge. Eine Zahl in Klammern bedeutet, dass die Methode auf einen der zusätzlich angegebenen Parameter verweisen sollte: {0} bedeutet also »Gib den nächsten Parameter aus«, {1} bedeutet »Gib den darauf folgenden Parameter aus« usw. Man könnte anstelle der numerischen Bezeichner auch einfach den Parameter selbst verwenden.
80
Das objektorientierte Windows-Formular
Um diese Methode verwenden zu können, müssen Sie Ihre Anwendung als reguläre ausführbare Datei kompilieren (verwenden Sie dabei die Option /t:exe). Die Methode ReferenceEquals ist ein nur-statisches Element, das der Equals-Methode ähnelt. Sie unterscheiden sich dahingehend, dass ReferenceEquals zwei Objekte miteinander vergleicht, um herauszufinden, ob sie sich in der gleichen Instanz befinden, also nicht, ob sie den gleichen Wert besitzen. Das lässt sich deswegen bewerkstelligen, weil in Computerbegriffen eine Instanz durch ihren Ort im Speicher (ihre Speicheradresse) definiert ist. Wenn also zwei Objekte auf dieselbe Speicheradresse verweisen, handelt es sich um dieselbe Instanz. Zwei verschiedene Speicheradressen können den gleichen Wert haben, aber es handelt sich nicht um dieselbe Instanz. (Beachten Sie, dass null – oder Nothing in VB .NET – stets mit derselben Instanz wie ein anderes null gleichzusetzen ist. Werfen Sie einen Blick auf Listing 3.2, das unterschiedliche Variablen miteinander vergleicht. Listing 3.2: Referenzen vergleichen 1: 2: 3: 4: 5: 6: 7: 8:
object a = null; object b = null; object c = new Object(); Console.WriteLine(Object.ReferenceEquals(a, b)); Console.WriteLine(Object.ReferenceEquals(a, c)); a = c; Console.WriteLine(Object.ReferenceEquals(a, c));
Zeile 5 liefert true, weil sich alle null-Werte auf dieselbe Instanz beziehen und einander sind. Zeile 6 hingegen gibt false bzw. unwahr zurück, weil a und c zwei unterschiedliche Objekte sind. Zeile 7 sieht aus, als würde sie a und c demselben Wert zuordnen, aber wenn Sie die eine Variable einer anderen zuweisen, setzen Sie eigentlich die Speicheradressen miteinander gleich, und daher liefert Zeile 8 true oder wahr. Falls Sie bereits über Programmiererfahrung verfügen, wissen Sie, woher der Name ReferenceEquals kommt: Diese Methode vergleicht die Speicheradressen (bzw. Referenzen) der Variablen statt ihre Werte miteinander. In den meisten Fällen genügt die Equals-Methode, aber es ist hilfreich, auch über die ReferenceEquals-Methode verfügen zu können. Die nächsten drei Methoden sind alle nicht-statisch. GetHashCode liefert einen Hash-Code für Ihr Objekt. Ein Hash-Code ist eine numerische Darstellung eines Objekts und hat darüber hinaus keine Bedeutung an sich. Die Objekte A und B können unterschiedliche
81
Mit Windows Forms arbeiten
Objekte sein, aber den gleichen Hash-Code generieren, wie etwa in Zeile 7. Sie können diesen Vorgang nicht umkehren und von der Zahl 7 auf ein Objekt schließen; Hashing ist ein Einbahnstraßen-Mechanismus. Wie ihr Name schon sagt, liefert die Methode GetType einfach nur den spezifischen Datentyp eines Objekts. Das ist sehr nützlich, wenn man nicht weiß, mit welcher Art von Daten man es zu tun hat, so etwa, wenn eine Funktion ein nicht identifizierbares Objekt liefert: string a = "hallo"; Console.WriteLine(a.GetType());
Dieses Codestück wird System.String liefern, den Datentyp der Variablen a. Beachten Sie, dass diese Methode einen Datentyp zurückgibt, nicht etwa nur den Namen des Typs: Sie liefert einen Typ und keine Zeichenfolge. Diese Methode wird häufig zum Vergleichen von Datentypen verwendet, etwa im Zusammenhang mit der eben besprochenen Methode GetConfig: myType myVariable; if (myVariable.GetType() != ConfigurationSettings.GetConfig ("mySettings").GetType()) return false;
Dieser Code prüft, ob der von der GetConfig-Methode gelieferte Typ (ein Type-Objekt) der gleiche Typ ist wie myVariable. Und in diesem Fall ist er es nicht. Zum Schluss kommen wir auf die gebräuchlichste Methode zu sprechen: ToString liefert die Zeichenfolgendarstellung eines Variablentyps. Das folgende Codefragment würde beispielsweise die Zeichenfolge "system.object" in der Befehlszeile ausgeben: Object objA = new Object(); Console.WriteLine(objA.ToString());
Beachten Sie, dass hier nicht der Wert eines Objekts ausgegeben wird, sondern der Typname. Manchmal wird die ToString-Methode für eine bestimmte Klasse überschrieben. Der string-Datentyp überschreibt beispielsweise ToString, so dass er den tatsächlichen Wert der Variablen ausgibt statt den Namen des Typs. Der folgende Code gibt die Zeichenfolge "hello world" aus: string strA = "hello world"; Console.WriteLine(strA.ToString());
Formular-Eigenschaften Das Objekt System.Windows.Forms.Form verfügt über genau 101 Eigenschaften, die es Ihnen gestatten, beinahe jeden Aspekt des Formulars zu steuern. Da dies zu viele Eigenschaften für nur einen Tag sind, lassen Sie uns nur auf ein paar der nützlicheren Aspekte einen Blick werfen, gruppiert nach Funktion.
82
Das objektorientierte Windows-Formular
Größe und Position steuern Ihnen sind bereits zwei Möglichkeiten, die Formulargröße festzulegen, bekannt: die Eigenschaften Width und Height (Breite bzw. Höhe). Diese Eigenschaften übernehmen lediglich Zahlenwerte, um die Formulargröße in Pixelwerten festzulegen. Zum Beispiel: form1.Width form1.Height = 200
Sie können die Größe auch mit Hilfe der Size-Eigenschaft und eines Size-Objekts festlegen: form1.Size = New Size(100, Form1.Size.Height) form1.Size = New Size(100, Form1.Size.Width)
Beide Methoden bewirken das Gleiche, aber meistens werden Sie die erste verwenden, da sie einfacher ist. Sie können die Größe auch abhängig von der Bildschirmgröße des Benutzers festlegen, und zwar, indem Sie das Screen-Objekt verwenden. Listing 3.3 zeigt dafür ein Beispiel. Listing 3.3: Das Festlegen der Formularhöhe abhängig von der Bildschirmgröße (in VB .NET) 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15:
Imports System Imports System.Windows.Forms Public Class MultiForm : Inherits Form Public Sub New Me.Text = "Hauptformular" Me.Height = Screen.GetWorkingArea(Me).Height / 2 End Sub End Class Public Class StartForm Public Shared Sub Main() Application.Run(New MultiForm) End Sub End Class
Dieser Quellcode dürfte Ihnen mittlerweile vertraut sein, daher betrachten wir lediglich Zeile 7 etwas genauer. Die Methode GetWorkingArea des ScreenObjekts liefert ein Rectangle-Objekt, das für den Benutzerbildschirm steht. Sie übergeben das aktuelle Formularobjekt (mit Hilfe des Me-Schlüsselworts), um der CLR mitzuteilen, welchen Bildschirm sie verwenden soll (nur für den Fall, dass der Benutzer mehr als einen Bildschirm besitzt). Rectangle-Objekte haben wiederum Height- und Width-Eigenschaften, welche ihrerseits Integer liefern, die die
83
Mit Windows Forms arbeiten
Höhe und Breite des Bildschirms beschreiben. Danach teilen wir den Höhe-Wert durch 2. Kompilieren Sie diese Anwendung und führen Sie sie aus. Beachten Sie, dass das Fenster jetzt die halbe Höhe Ihres Bildschirms beansprucht. Um festzulegen, wo das Fenster sich öffnet, können Sie die Eigenschaften Location, DesktopLocation, Top oder Left verwenden. Die letzten beiden legen die Position der oberen linken Ecke Ihres Formulars auf dem Bildschirm fest. Sie funktionieren genauso wie die Height- und Width-Eigenschaften. Beachten Sie, dass Sie diese Eigenschaften auf eine Position festlegen können, die sich weit außerhalb des Bildschirms befindet, etwa auf Top = 9999 und Left = 9999. Die Eigenschaften Location und DesktopLocation sind einander ähnlich. Bei einem FormObjekt bewirken sie das Gleiche: Sie legen die Anfangsposition der oberen linken Ecke des Formulars fest. Die beiden folgenden Zeilen realisieren beispielsweise die gleiche Funktionalität: Form1.DesktopLocation = New Point(100,300) Form1.Location = New Point(100,300) Location ist eine Eigenschaft, die von Control, der Urgroßelternklasse von Form, geerbt
wird. Diese Eigenschaft verwendet man, um die Position eines Steuerelements innerhalb eines anderen Steuerelements festzulegen. In unserem Fall ist das beinhaltende Steuerelement für das Formular einfach der Benutzerbildschirm. Sie könnten die Location-Eigenschaft für jedes beliebige Objekt verwenden, das vom Control-Objekt erbt, etwa für die Steuerelemente TextBox oder Label (Beschriftung), um seine Position innerhalb eines anderen Objekts zu festzulegen – zum Beispiel Ihres Formulars. Die Eigenschaft DesktopLocation gilt hingegen nur für das Form-Objekt. Der Einheitlichkeit halber werden wir ab jetzt die Eigenschaft Location verwenden. Daher brauchen Sie nur eine Eigenschaft für alle Windows Forms-Objekte zu verwenden.
Das Aussehen steuern Sie wissen bereits über die Text-Eigenschaft Bescheid, die den in der Titelzeile angezeigten Text steuert. Lassen Sie uns daher ein paar Dinge betrachten, die Sie mit Schriftarten (Fonts) anstellen können. Die Eigenschaft Font legt die Schriftart fest, die im Formular benutzt wird, es sei denn, die Font-Eigenschaft eines Steuerelements hätte Priorität. Die Eigenschaft ForeColor legt die Textfarbe fest. Betrachten Sie das folgende Codefragment: Form1.Font = New Font(new FontFamily("WingDings"), 23) Form1.ForeColor = Color.Blue
Die erste Zeile erzeugt ein neues Font-Objekt (Sie merken schon, dass absolut alles in .NET ein Objekt ist, selbst Schriftarten und Farben). Es gibt eine ganze Reihe unterschiedlicher
84
Das objektorientierte Windows-Formular
Möglichkeiten, um Font-Objekte zu erzeugen (in anderen Worten: Es hat viele Konstruktoren), und dies ist lediglich eine davon. Sie legen die Schriftart fest, indem Sie das Objekt FontFamily verwenden, das vordefinierte Schriftartnamen enthält. Der zweite Parameter besteht in der Größe der Schriftart (in der Maßeinheit »Punkt«). Die zweite Zeile legt die Textfarbe mit Hilfe der Blue-Eigenschaft des Color-Objekts als blau fest. Wird dieser Code in einem Windows Forms-Formular verwendet, erzeugt er die Ausgabe, die Sie in Abbildung 3.2 sehen (Sie müssen mir glauben, dass die Farbe wirklich blau ist).
Abbildung 3.2: Aussehen und Farbe einer Schriftart werden in ihren jeweiligen Eigenschaften festgelegt.
Die Eigenschaften BackColor und BackgroundImage erlauben Ihnen, das vorgegebene Erscheinungsbild eines Formulars zu ändern. BackColor wird genauso verwendet wie ForeColor: Form1.BackColor = Color.Lachsrosa
Die Eigenschaft BackgroundImage übernimmt ein Image-Objekt als Parameter. Üblicherweise verwenden Sie die FromFile-Methode des Image-Objekts, um eine Grafik zu laden; Sie müssen einen Pfadnamen angeben, z.B. so: Form1.BackgroundImage = Image.FromFile("c:\winforms\day3\kaffeebohne.bmp") FromFile ist eine statische Methode, wie Sie wahrscheinlich schon herausgefunden haben, da Sie ja keine neue Image-Instanz erzeugen müssen, um sie verwenden zu können. Wenn wir von Abbildung 3.2 ausgehen, erhalten wir nun einen gekachelten Hintergrund, wie man in Abbildung 3.3 sehen kann.
Die Hintergrundfarbe des Label-Objekts (Bezeichnungsfeld) ist immer noch grau. An Tag 6 lernen Sie, wie Sie dies ändern können. Eine weitere Bildeigenschaft, die Sie anpassen können, ist die Icon-Eigenschaft. Das Icon oder Symbol wird in der oberen linken Ecke der Titelzeile Ihres Formulars verwendet, ebenso wie in jeder anderen Darstellung Ihrer Anwendung, wie etwa der Windows-Taskleiste. So legen Sie die Eigenschaft fest: Me.Icon = New Icon("c:\winforms\day3\VSProjectApplication.ico")
85
Mit Windows Forms arbeiten
Abbildung 3.3: Sie können eine Grafik dazu verwenden, den Hintergrund Ihres Formulars damit zu kacheln.
Sie müssen eine neue Instanz des Icon-Objekts erzeugen, wobei Sie einen gültigen Pfad zu einer Grafik angeben. Die Grafik, die Sie auswählen, muss eine Icon-Datei (.ico) sein, sonst erzeugt Ihre Anwendung eine Fehlermeldung. Das Bild des Mauszeigers lässt sich durch die Cursor-Eigenschaft steuern: Me.Cursor = Cursors.Hand
Das Cursors-Objekt verfügt über eine Menge Eigenschaften für den Standardsatz an Windows-Mauszeigern, darunter Arrow, IBeam, WaitCursor und Help. In der .NET-Dokumentation finden Sie weitere Mauszeiger. Sie können auch einen eigenen Mauszeiger aus einer Datei laden: Me.Cursor = New Cursor("Pfadname")
Die Mauszeigerdatei muss die Endung .cur tragen. Animierte Mauszeiger (jene mit der Endung .ani) werden von der CLR nicht unterstützt. Die Eigenschaft ShownInTaskBar legt fest, ob Ihre Anwendung in der Windows-Taskleiste zu sehen sein soll (dies betrifft nicht das Fenster am Bildschirm, sondern nur die Schaltfläche in der Taskleiste). Der Standardwert lautet true (wahr). Unter bestimmten Gegebenheiten möchten Sie diese Einstellung vielleicht ändern, um einen Anwender daran zu hindern, ein Formular auszuwählen. Wenn Sie etwa einen so genannten »Splash Screen« erzeugen, der etwa das Logo Ihrer Firma anzeigt, wollen Sie wahrscheinlich nicht, dass der Benutzer es auswählt (es verschwindet ja auch sofort). Setzen Sie dann einfach die Eigenschaft ShownInTaskBar auf false, und der Benutzer kann es nicht mehr aus der Taskleiste auswählen. Die Eigenschaft FormBorderStyle legt das Aussehen der Umrisslinie einer Windows FormsFormulars fest. Hauptsächlich ändern Sie diese Eigenschaft, um die Größenänderung des Formulars zu erlauben oder zu unterbinden. Manchmal betrifft das Ändern einer Umrisslinie auch das Aussehen des Formulars, wie zum Beispiel: Form1.FormBorderStyle = FormBorderStyle.Sizable
86
Das objektorientierte Windows-Formular
Die Aufzählung für FormBorderStyle (nicht zu verwechseln mit der Eigenschaft Form.FormBorderStyle) führt sieben verschiedene Typen von Umrisslinien auf, wie in Tabelle 3.1 zu sehen. (Eine Aufzählung ist einfach eine Sammlung von Eigenschaften oder Stilen.) Abbildung 3.4 zeigt eine Sammlung der unterschiedlichen Stile.
Abbildung 3.4: Es stehen sieben vordefinierte Umrisslinienstile zur Auswahl.
Stil
Beschreibung
Fixed3D
Nicht skalierbar. Dreidimensionale Umrisslinie.
FixedDialog
Nicht skalierbar. Dicke Umrisslinie.
FixedSingle
Nicht skalierbar. Dünne Umrisslinie.
FixedToolWindow
Formular mit schmalerer Titelleiste. Nützlich für die Anzeige von QuickInfos und Hilfefenstern. Nicht skalierbar. Enthält keine Maximieren- oder Minimieren-Schaltflächen.
None
Nicht skalierbar. Keine Umrisslinie.
Sizable
Skalierbar. Standardstil.
SizableToolWindow
Formular mit schmalerer Titelleiste. Nützlich für die Anzeige von QuickInfos und Hilfefenstern. Skalierbar. Enthält keine Maximieren- oder MinimierenSchaltflächen.
Tabelle 3.1: Stile für FormBorderStyle
87
Mit Windows Forms arbeiten
Zum Schluss gibt es noch drei Eigenschaften, die steuern, wie und ob Ihr Formular am Bildschirm angezeigt wird. Die Visible-Eigenschaft legt fest, ob Ihr Formular für den Benutzer sichtbar ist. Ist ein Formular nicht zu sehen, kann man nicht damit interagieren. Auf diese Weise lassen sich bestimmte Dinge vor dem Anwender verbergen, etwa wenn Sie Ihre Anwendung geöffnet lassen wollen, sie aber nicht mit der Benutzeroberfläche interferieren soll. Standardmäßig ist ein solches Formular nicht sichtbar. Um ein Formular teilweise sichtbar zu machen (mit anderen Worten: transparent), müssen Sie die Eigenschaft Visible auf true (wahr) setzen und die Eigenschaft Opacity (Undurchsichtigkeit) verwenden. Diese Eigenschaft übernimmt einen Wert zwischen 1 (= völlig undurchsichtig, also völlig sichtbar) und 0,0 (= unsichtbar bzw. völlig durchsichtig). Um ein paar wirklich interessante Transparenztechniken vorzuführen, können Sie die Eigenschaft TransparencyKey so einstellen, dass eine bestimmte Farbe durchsichtig sein soll. Der folgende Code macht beispielsweise alle grauen Flächen im Formular unsichtbar, während alles andere undurchsichtig bleibt: Form1.TransparencyKey = Color.Gray
Wenn Sie nun auch noch die BackColor des Formulars auf Grau setzen, erhalten Sie schließlich ein Formular wie das in Abbildung 3.5 zu sehende (das Formular wurde über ein Fenster mit Befehlszeilenausgabe gelegt, um den Transparenzeffekt zu verdeutlichen).
Abbildung 3.5: Verwenden Sie die Eigenschaft TransparencyKey, um nur eine bestimmte Farbe sichtbar zu machen.
Interaktivität steuern Wenn Sie eine Windows-Anwendung betrachten, dann sehen Sie an deren Fenstern die Standardmerkmale: In der oberen rechten Ecke befinden sich Schaltflächen für das Minimieren und Maximieren sowie das Schließen des Fensters, manchmal auch eine HILFESchaltfläche. Rechts unten befindet sich oft ein Anfasser, damit man mit der Maus die Fenstergröße verändern kann. Diese Systemmenüfelder sehen Sie in Abbildung 3.6.
88
Das objektorientierte Windows-Formular
Jedes Systemmenüfeld lässt sich mit Hilfe der folgenden Eigenschaften in Ihrem Formular verstecken oder anzeigen: 쐽
MaximizeBox
쐽
MinimizeBox
쐽
HelpButton
쐽
ControlBox
쐽
SizeGripStyle
Abbildung 3.6: Diese Systemmenüfelder gehören zu den Standardmerkmalen von Windows.
Die ersten vier Eigenschaften nehmen lediglich den Wert true oder false an. ControlBox legt fest, ob die vorhergehenden Schaltflächen überhaupt angezeigt werden sollen. Seien Sie hier jedoch vorsichtig: Wenn Sie ControlBox auf false setzen, könnte es sein, dass Sie Ihre Anwendung nicht mehr schließen können!
Die HILFE-Schaltfläche taucht nur auf, wenn die MINIMIEREN- und MAXIMIEREN-Schaltflächen nicht sichtbar sind. Dies ist ein Standardmerkmal von .NET.
Die Eigenschaft SizeGripStyle nimmt einen SizeGripStyle-Aufzählungswert an: Auto, Hide oder Show. Auto zeigt den Skalierungsanfasser an, falls nötig (also abhängig vom FormBorderStyle), während Hide und Show die Größenziehpunkge (Skalierungsanfasser) jeweils verbergen oder zeigen. Mit einer Anwendung sind häufig zwei besondere Tasten verbunden: (¢) und (ESC). Viele Anwendungen werden geschlossen, wenn Sie die (ESC)-Taste drücken. Sie können diese Funktionen mit den Eigenschaften AcceptButton und CancelButton steuern. Listing 3.4 zeigt in C# ein Beispiel, wie man diese Eigenschaften verwendet.
89
Mit Windows Forms arbeiten
Listing 3.4: Die Eigenschaften AcceptButton und CancelButton 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44:
90
using System; using System.Windows.Forms; using System.Drawing; namespace TYWinForms.Day3 { public class Listing34 : Form { Button btAccept = new Button(); Button btCancel = new Button(); Label lblMessage = new Label(); public Listing34() { lblMessage.Location = new Point(75,150); lblMessage.Width = 200; btAccept.Location = new Point(100,25); btAccept.Text = "OK"; btAccept.Click += new EventHandler(this.AcceptIt); btCancel.Location = new Point(100,100); btCancel.Text = "Abbrechen"; btCancel.Click += new EventHandler(this.CancelIt); this.AcceptButton = btAccept; this.CancelButton = btCancel; this.Text = "Beispiel für die Buttons OK und Abbrechen"; this.Height = 200; this.Controls.Add(lblMessage); this.Controls.Add(btAccept); this.Controls.Add(btCancel); } public void AcceptIt(Object Sender, EventArgs e) { lblMessage.Text = "Der OK-Button wurde gedrückt"; } public void CancelIt(Object Sender, EventArgs e) { lblMessage.Text = "Der Abbrechen-Button wurde gedrückt"; } } public class StartForm { public static void Main() { Application.Run(new Listing34());
Das objektorientierte Windows-Formular
45: 46: 47: 48:
} } }
Ein Großteil dieses Codes dürfte bereits vertraut aussehen, daher können wir das meiste davon überfliegen. Die Zeilen 1 bis 3 importieren die notwendigen Namensräume. Zeile 5 deklariert den Namensraum, zu dem diese Anwendung gehört; gemäß unserem Bezeichnungsschema heißt dieser Namensraum TYWinForms.Day3. Zeile 7 deklariert die Windows Forms-Formularklasse. Die Zeilen 8 bis 10 deklarieren die Steuerelemente, die wir im Formular verwenden werden. Beachten Sie, dass sie außerhalb einer Methode deklariert werden, doch innerhalb einer Klasse. Dadurch lassen sie sich von jeder Methode in der Klasse verwenden. Im Konstruktor ab Zeile 12 findet der Großteil des Geschehens statt. Der Code in den Zeilen 13 bis 22 setzt einfach ein paar Eigenschaften für unsere Steuerelemente. Beachten Sie insbesondere die Zeilen 18 und 22, die auf Methoden zeigen, die ausgeführt werden, sobald eine der beiden Schaltflächen im Formular angeklickt wird. Diese Methoden, AcceptIt und CancelIt, befinden sich in den Zeilen 33 und 37 und geben einfach nur eine Meldung im Label-Steuerelement aus. Die Zeilen 24 bis 30 stellen einige Eigenschaften ein und fügen dem Formular die Steuerelemente hinzu. Die Zeilen 24 und 25 legen die Eigenschaften AcceptButton und CancelButton für die jeweilige OK- oder Abbrechen-Schaltfläche fest. Im Wesentlichen bewirkt das Anklicken der OK- oder Abbrechen-Schaltfläche das Gleiche wie das Drücken der (¢)- oder der (ESC)-Taste. Schließlich enthalten die Zeilen 42 bis 46 eine weitere Klasse. Sie dient lediglich dazu, unsere Main-Methode aufzunehmen und die Methode Application.Run aufzurufen. Die Ausgabe der Anwendung nach dem Drücken der (ESC)-Taste wird in der Schaltfläche AcceptButton und Abbildung 3.7 gezeigt. Erhält die Abbrechen-Schaltfläche im Formular den Fokus, veranlasst das Drücken der (¢)-Taste das »Anklicken« oder Niederdrücken dieser Schaltfläche und folglich das Auslösen der CancelIt-Methode. Mit anderen Worten: Die Schaltfläche erhält die Eingabe noch vor dem eigentlichen Formular. Leider gibt es für diese missliche Tatsache keinen einfachen Ausweg. Manche Steuerelemente, wie etwa die Button- und RichTextBox-Steuerelemente, behandeln das Drücken der (¢)-Taste automatisch, wenn sie den Fokus haben, also noch bevor irgendetwas anderes ausgeführt werden kann. Sobald Sie dem Abbrechen-Button den Fokus geben (indem Sie ihn anklicken oder mit der Tabulatortaste darauf gehen), bewirkt die (¢)-Taste stets die Ausführung der CancelIt-Methode.
91
Mit Windows Forms arbeiten
Falls es Sie interessiert, können Sie diese Verhaltensweise umgehen, indem Sie das Button-Steuerelement selbst überschreiben. Wir werden dies an Tag 18 behandeln. Obwohl die AcceptButton- und CancelButton-Eigenschaften auf Button-Steuerelemente verweisen müssen, beachten Sie bitte, dass diese Buttons nicht unbedingt für den Benutzer zu sehen sein müssen. Indem man die Visible-Eigenschaft auf false setzt, macht man die Schaltflächen unsichtbar, ohne ihre Funktionalität zu verlieren. Die Eigenschaft AllowDrop schließlich legt fest, ob das Formular mit Drag & Drop-Funktionen umgehen kann, d.h. wenn ein Anwender mit der Maus ein Objekt in das Formular zieht und dort »fallen« lässt. Diese Eigenschaft kann einen true- oder false-Wert annehmen. Wie Sie Ihr Formular dazu bringen, etwas bei einem solchen Ereignis auszulösen, besprechen wir heute später im Abschnitt »Ereignisse im Formular«.
Abbildung 3.7: Das Drücken der (ESC)-Taste hat die gleiche Wirkung wie das Anklicken der ABBRECHEN-Schaltfläche.
Um eine vollständige Liste der Eigenschaften des Form-Objekts nachzuschlagen, lesen Sie Anhang B »Windows Forms-Steuerelemente«.
Formular-Methoden Das Form-Objekt verfügt ebenfalls über zahlreiche Methoden: über 57, um genau zu sein. In der Mehrzahl sind sie von den Klassen Object und Control geerbt, so dass sie den meisten Objekten, mit denen Sie in Windows Forms arbeiten, zur Verfügung stehen. Eingeteilt nach Kategorien sollen einige davon hier vorgestellt werden. Neben den hier behandelten Methoden sollten Sie auch die bereits vorgestellten ObjectMethoden wie Equals und ToString nicht vergessen. Das Form-Objekt kann auch sie nutzen.
Aspekte der Bildschirmanzeige Die ersten zwei Methoden sind Show und Hide. Diese beiden Funktionen machen Ihr Formular jeweils sichtbar oder unsichtbar, indem sie die Visible-Eigenschaft ändern. Sie verändern das Formular selbst nicht – etwa das Entfernen aus dem Speicher oder der Aufruf anderer Funktionen –, sondern nur, ob der Benutzer es sehen kann.
92
Das objektorientierte Windows-Formular
Die Close-Methode jedoch entsorgt ein Formular (und seine Steuerelemente) vollständig durch das Entfernen aus dem Speicher. Setzen Sie diese Methode ein, wenn Sie Ihre Anwendung schließen wollen oder wenn Sie einfach kein Formular mehr brauchen. Da Windows ja ein Multitasking-Betriebssystem ist, dürfen viele Fenster gleichzeitig geöffnet sein. Jedes Fenster muss um die Aufmerksamkeit des Benutzers wetteifern. Das FormObjekt verfügt über einige Methoden, die Ihnen helfen, diese Schwierigkeit zu bewältigen. Die Activate-Methode »aktiviert« Ihr Formular. Das kann zweierlei bedeuten: 쐽
Wenn Ihre Anwendung die aktive ist, also vom Benutzer gerade verwendet wird, stellt Activate das Formular in den Vordergrund der Bildschirmanzeige, so dass es über allen anderen Fenstern liegt.
쐽
Falls es nicht die aktive Anwendung ist, blinken die Titelleiste und das Symbol in der Taskleiste, um die Aufmerksamkeit des Benutzers auf sich zu lenken. Wahrscheinlich haben Sie diese Art von Blickfang schon einmal gesehen; die gebräuchlichste Verwendung findet sich bei Instant-Messaging-Anwendungen. Hat Ihnen jemand eine Instant Message (IM) gesendet, während Sie gerade in einer anderen Anwendung arbeiten, öffnet sich plötzlich das IM-Fenster im Hintergrund und seine Titelleiste blinkt.
Beim Blickfang noch etwas direkter als Activate sind die Methoden BringToFront und SendToBack. Die erste Methode stellt ein Formular vor alle anderen Fenster in der Bildschirmanzeige, so dass der Benutzer gezwungen ist, es anzusehen. Das ist nützlich, falls etwas mit Ihrer Anwendung geschieht, das die volle Aufmerksamkeit des Benutzers verlangt (beispielsweise der Erhalt einer Instant Message). Umgekehrt stellt SendToBack ein Formular hinter alle anderen Fenster auf dem Bildschirm. SendToBack wird zwar nicht häufig benutzt, ist aber für alle Fälle vorhanden. Sie können die Eigenschaft TopMost auf true setzen, um dafür zu sorgen, dass Ihr Formular immer im Vordergrund steht. Besonders nützlich ist dieses Leistungsmerkmal, wenn Alarm- oder Fehlermeldungen ausgegeben werden sollen. Schließlich funktioniert die Refresh-Methode ähnlich wie die Schaltfläche AKTUALISIEREN in Ihrem Web-Browser: Sie zeichnet alles im Formular neu und aktualisiert mitunter sogar dessen Inhalte. Wir werden näher auf diese Methode zu sprechen kommen, wenn wir GDI+ an Tag 13 durchnehmen.
Eigenschaften zurücksetzen Das Form-Objekt verfügt über eine Reihe von Reset-Methoden, mit denen Sie geänderte Eigenschaften auf ihre Vorgabewerte zurücksetzen können. Alle diese Methoden folgen dem Bezeichnungsschema ResetEigenschaft. Einige der gebräuchlicheren sind: 쐽
ResetBackColor
쐽
ResetCursor
93
Mit Windows Forms arbeiten
쐽
ResetFont
쐽
ResetForeColor
쐽
ResetText
Diese Methoden sind sehr nützlich, wenn man etwas geändert hat und alles wieder rückgängig machen muss, man aber nicht weiß oder sich nicht darum kümmert, welches jeweils der ursprüngliche Wert war. Wenn der Benutzer beispielsweise in einem Textprogramm mehrmals die Schriftart ändert, aber dann wieder zu den Standardwerten zurückkehren möchte, könnte man ResetFont einsetzen.
3.2
Ereignisbehandlung
Ein Ereignis (event) ist etwas, das als Ergebnis einer Aktion oder Aktivität passiert. Greifen Sie auf die Auto-Analogie zurück und stellen Sie sich bitte vor, dass Sie auf die Bremse treten (eine Aktion). Der Wagen hält an (ein Ereignis). Treten Sie auf das Gaspedal (Aktion), fährt der Wagen wieder an (Ereignis). Ein Ereignis ist stets die Folge einer Aktion, die stattgefunden hat. Auch Windows Forms verfügen über Ereignisse, obwohl so manches davon nicht besonders augenfällig ist. Öffnen und Schließen etwa sind zwei Ereignisse, die erfolgen, wenn Sie Ihre Anwendung starten beziehungsweise beenden. Schieben Sie Ihren Mauszeiger über das Formular, findet ein Ereignis statt, und wenn Ihr Mauszeiger es wieder verlässt, findet ein weiteres Ereignis statt. Ohne Ereignisse wären Windows Forms ziemlich fade, denn sie würden nie etwas bewirken, ganz gleich, wie sehr der Anwender das auch erzwingen wollte.
Die Nachrichtenschleife Ereignisse eignen sich in wunderbarer Weise dafür, Benutzereingaben zu handhaben. Lassen Sie uns zwei verschiedene Anwendungsmodelle betrachten – eines mit, das andere ohne Ereignisse –, bevor wir untersuchen, wie Anwendungen Ereignisse verwenden. Stellen Sie sich zunächst die ereignisgesteuerte Anwendung vor. Unter »ereignisgesteuert« versteht man, dass die Anwendung auf Ereignisse reagiert, die auf Benutzeraktionen hin erfolgen. Ohne diese Ereignisse würde die Anwendung in der Tat überhaupt nichts tun. Die Ereignisse treiben die Anwendung quasi an. In diesem Modell ist die Anwendung untätig und wartet darauf, dass etwas passiert. Sie verwendet Ereignisse als ihr Stichwort, eine Aktion auszuführen. Ein Benutzer drückt beispielsweise eine Taste. Die Anwendung merkt, dass ein Ereignis stattgefunden hat
94
Ereignisbehandlung
(Tastendruck), führt eine Aktion aus, um das entsprechende Zeichen anzuzeigen, und wartet dann auf das nächste Ereignis. Eine Anwendung, die nicht ereignisgesteuert ist, gestattet dem Benutzer nicht die gewünschte Nutzung der Anwendung, vielmehr kann er lediglich auf ihre Aufforderungen reagieren. Mit einer ereignisgesteuerten Anwendung hingegen kann der Benutzer interagieren, ganz gleich, mit welchem ihrer Teile, zu welcher Zeit oder in welcher Reihenfolge. Stellen Sie sich einmal eine Taschenrechner-Anwendung vor, die nicht ereignisgesteuert wäre. Sobald Sie die Anwendung gestartet haben, holt sie zwei Werte aus den Textfeldern, führt mathematische Berechnungen aus und gibt das Ergebnis aus. Befinden sich jedoch keine Werte in den Textfeldern, tut der Taschenrechner überhaupt nichts. Er kann nicht feststellen, ob sich eine Zahl geändert hat, da er keine Ereignisse erkennt. Jedes Mal, wenn Sie die Zahlen ändern, um einen anderen Wert zu berechnen, müssen Sie zuerst die Zahlen ändern und dann die Anwendung erneut ausführen. Klassische Active Server Pages (also ASP als Vorgänger von ASP.NET) funktionieren auf diese Weise. Eine ASP-Anwendung wird ausgeführt, sobald ein Benutzer die Seite von seinem Browser aus anfordert, dann gibt sie den nötigen HTML-Code aus und hält an. Um das, was danach passiert, kümmern sich ASPs nicht, da sie nicht auf Ereignisse zu warten brauchen. Damit eine ASP-Anwendung Benutzereingaben verarbeitet, muss der Benutzer alle Eingaben in ein Webformular eintippen, bevor er es der ASP zur Verarbeitung zuschickt. Die ASP kennt nichts anderes als das, was ihr beim Start bekannt gegeben worden ist. Abbildung 3.8 illustriert diesen Vorgang. Nicht ereignisbasiert Anwendung starten
Benutzereingaben liefern
Verarbeitung
Stop
Abbildung 3.8: Anwendungen, die nicht ereignisgesteuert sind, erfordern einen dreistufigen Prozessablauf.
Beide Modelle haben ihre Vor- und Nachteile. Sobald sie einmal gestartet sind, laufen Anwendungen, die nicht ereignisgesteuert sind, ohne Benutzereingaben ab. Ereignisgesteuerte Anwendungen erfordern hingegen üblicherweise eine Benutzereingabe, sind aber häufig reaktionsfähiger und interaktiv. Da aber Interaktion eine zwingende Eigenschaft einer jeden Windows-basierten Anwendung ist, werden alle Ihre Programme das ereignisgesteuerte Modell verwenden.
95
Mit Windows Forms arbeiten
Wie also entdeckt eine Anwendung, dass ein Ereignis stattgefunden hat? Sobald eine typische Windows-Anwendung gestartet wird, tritt sie in eine Nachrichtenschleife ein. Dies ist lediglich eine Zeitspanne, in der die Anwendung auf eine Eingabe wartet oder auf Mitteilungen vom Benutzer (einen Befehl etwa). Die Zeitspanne dauert so lange, bis die Anwendung beendet wird, so dass dies als eine Schleife (oder Zyklus) bezeichnet wird. Während dieser Schleife tut die Anwendung nichts, außer auf eine Benutzereingabe zu warten (die Spanne der Nicht-Aktivität wird Leerlauf bzw. idling genannt). Sobald eine Eingabe entgegengenommen wird, verrichtet die Anwendung ihr Werk und fällt dann wieder zurück in ihre Nachrichtenschleife. Dieser Zyklus setzt sich wieder und wieder fort, bis die Anwendung geschlossen wird. Wenn Sie etwas eingeben, ist das Windows-Betriebssystem die erste Stufe in der Verarbeitung. Windows entscheidet darüber, zu welcher Anwendung das Ereignis gehört, und schickt es ihr zu. Diese Nachrichten sind als »Windows-Nachrichten« bekannt, wovon die Bezeichnung »Nachrichtenschleife« abgeleitet wurde. Ereignisbasiert Anwendung starten
in Messageschleife eintreten
Verarbeitung
Benutzereingabe
Falls Benutzereingabe auf “Stop” lautet, dann
Stop
Abbildung 3.9: Die Nachrichtenschleife wartet auf eine Benutzereingabe.
Wenn Sie beispielsweise ein Microsoft Word-Dokument das erste Mal öffnen, passiert nichts. Word wartet auf Ihre Eingabe. Sobald Sie eine Taste drücken, findet ein Ereignis statt, eine Methode wird ausgeführt (um das Zeichen am Bildschirm anzuzeigen), dann fällt Word wieder in die Nachrichtenschleife zurück, um weiteren Input abzuwarten. Jedes Mal wenn Sie eine Taste drücken, hält die Nachrichtenschleife für einen Augenblick an, um etwas zu verarbeiten, und setzt dann das Warten fort. Abbildung 3.9 illustriert diesen Zyklus. Windows Forms sind üblicherweise die primäre Benutzeroberfläche Ihrer Anwendungen. Daher handhaben sie eine ganze Reihe unterschiedlicher Ereignisse.
96
Ereignisbehandlung
Form-Ereignisse Sie haben bereits gesehen, wie sich einige Ereignisse handhaben lassen. Im Beispiel des Taschenrechners aus der gestrigen Lektion nutzten Sie das Click-Ereignis des ButtonObjekts. Die Handhabung von Ereignissen in Windows Forms ist immer ziemlich die gleiche, ungeachtet der Objekte, denen die Ereignisse zugeordnet sind. An Tag 5 besprechen wir Ereignisse eingehender, wie man sie handhabt und welche Unterschiede zwischen verschiedenen Ereignissen bestehen. Nun lassen Sie uns aber fürs Erste einen Blick auf einige der 71 Ereignisse des Form-Objekts werfen.
Die Ausführung steuern Um Ihnen als Entwickler die größtmögliche Kontrolle über Ihre Anwendung zu verleihen, werden manche Ereignisse sowohl vor als auch nach dem Eintreten einer Aktion gestartet. Wenn Sie beispielsweise Ihr Formular schließen, findet das Closing-Ereignis direkt vor dem Schließen des Formulars statt, das Closed-Ereignis direkt danach. Diese Arten von Ereignisnamen folgen stets den Regeln für das Partizip Präsens bzw. Partizip Perfekt, daher »Closing« (»sich gerade schließend«) und »Closed« (also »geschlossen«). Nicht alle Ereignisse treten in solchen Paaren auf. Wenn Sie jedoch einen –ing-Ereignisnamen lesen, können Sie sicher sein, dass auch ein –ed-Ereignis folgt; das gilt aber nicht umgekehrt. Was ist der Grund für die Methode, zwei Ereignisse pro Aktion zu verwenden? Stellen Sie sich bitte einen großen Gemüseladen vor, der um 20:00 Uhr schließt. Fünf Minuten, bevor der Laden eigentlich zumacht, wird über die Lautsprecheranlage eine Ansage zu hören sein, die die Kunden bittet, dass sie sich zu den Kassen begeben möchten. Dieses Vor-Schließen-Ereignis dient dazu, die Kunden zu warnen, dass etwas geschehen wird. Um 20:00 Uhr schließt der Laden, und alle Kunden werden zum Verlassen des Ladens aufgefordert, alle Türen werden verriegelt. Diese zweistufige Vorgehensweise erlaubt es sowohl dem Ladeninhaber oder Filialleiter als auch den Kunden, sich auf das tatsächliche eintretende Ereignis des Ladenschlusses vorzubereiten. Bei Windows-Anwendungen verhält es sich ähnlich. Wenn z.B. ein Benutzer eine große Zahl von Änderungen an einem Word-Dokument vornimmt, dann die Anwendung mit Hilfe des SCHLIEßEN-Steuerelements schließt, aber das Speichern seines Dokuments vergisst, was würde dann geschehen? Wenn man auf das Closed-Ereignis warten würde, bevor man etwas unternähme, wäre es bereits zu spät: Das Dokument wäre schon geschlossen worden, und alle Änderungen wären verloren. In .NET jedoch wird das Closing-Ereignis ausgelöst, bevor das eigentliche Schließen des Dokuments eintritt. In diesem Fall können Sie nun den Benutzer auffordern, sein Dokument zu speichern, bevor die Änderungen verloren gehen, und dadurch die Änderungen zu erhalten. Sobald das Fenster geschlossen ist,
97
Mit Windows Forms arbeiten
wird das Closed-Ereignis ausgelöst. Danach können Sie jede Verarbeitungsmöglichkeit nutzen, die Sie benötigen (etwa dem Benutzer eine Meldung anzeigen). Lassen Sie uns einen Blick auf ein Beispiel werfen, in dem dieser zweistufige Vorgang verwendet wird. Listing 3.5 verwendet die Closing- und Closed-Ereignisse im Form-Objekt, um das obige Beispiel zu illustrieren. Listing 3.5: Die Kontrolle der Programmausführung mit Hilfe von Ereignissen in VB .NET 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31:
Imports Imports Imports Imports
System System.Windows.Forms System.Drawing System.ComponentModel
Namespace TYWinForms.Day3 public class Listing35 : Inherits Form public sub New() Me.Text = "Beispiel für ein Ereignis" AddHandler Me.Closing, AddressOf Me.ClosingMsg AddHandler Me.Closed, AddressOf Me.ClosedMsg end sub public sub ClosingMsg(Sender as Object, e as CancelEventArgs) Microsoft.VisualBasic.MsgBox("Formular schließt sich") end sub public sub ClosedMsg(Sender as Object, e as EventArgs) Microsoft.VisualBasic.MsgBox("Formular ist geschlossen") end sub end class public class StartForm public shared sub Main() Application.Run(new Listing35) end sub end class End Namespace
In Zeile 4 wird ein Namensraum importiert, der noch nicht aufgetreten ist: System.ComponentModel. Er verfügt über Objekte, die Ereignisse betreffen, die man weiter unten im Code benötigt. Die Zeilen 6 bis 10 sind nichts Besonderes. Die
98
Ereignisbehandlung
Zeilen 11 und 12 verwenden die AddHandler-Methode (die Ihnen an den Tagen 1 und 2 begegnet ist), um der CLR zu befehlen, die Methoden ClosingMsg und ClosedMsg (in den Zeilen 15 und 19) auszuführen, wenn die Closing- und Closed-Ereignisse jeweils ausgelöst werden. Wir springen in Zeile 15 zur Closing-Methode, die ausgeführt wird, sobald das Closing-Ereignis ausgelöst worden ist und sich das Formular tatsächlich schließt. Sehen Sie sich zunächst die Signatur (die erste Zeile) dieser Methode an. Sie übernimmt zwei Parameter: ein Object- und ein System.ComponentModel.CancelEventArgs-Objekt. Sie wissen bereits, was das Object-Objekt ist. Der zweite Parameter ist ein spezialisiertes Objekt, das nur für Ereignisse gilt, insbesondere für Closing-Ereignisse. Es verfügt über eine besondere Eigenschaft namens Cancel, um damit den Vorgang – in diesem Fall das Schließen des Formulars – zu unterbrechen, falls dies nötig sein sollte (wie etwa im Beispiel des Textprogramms). Wenn Sie festlegen, dass das Schließen gestoppt werden soll (etwa weil der Benutzer vergessen hat, sein Dokument zu speichern), setzen Sie die Cancel-Eigenschaft auf true: e.Cancel = true
Die Anwendung wird mit dem Schließen innehalten und in die Nachrichtenschleife zurückkehren. In unserem Fallbeispiel befassen wir uns nicht damit zu verhindern, dass das Formular geschlossen wird. In Zeile 16 rufen wir vielmehr eine Methode auf, die eine Meldung auf dem Bildschirm anzeigt. Die MsgBox-Methode des Namensraums Microsoft.VisualBasic präsentiert lediglich ein Popup-Feld mit dem festgelegten Text darin. (Beachten Sie, dass wir anstatt den vollen Namen der MsgBox-Methode in Zeile 16 zu verwenden, auch den Namensraum Microsoft.VisualBasic mit Hilfe von Imports hätten importieren können.) In Zeile 19 beginnt die Methode ClosedMsg. Sie wird ausgeführt, nachdem das Formular sich geschlossen hat. Beachten Sie, dass sie als zweiten Parameter ein EventArgs-Objekt statt CancelEventArgs übernimmt. Den Grund dafür besprechen wir an Tag 5. Diese Methode wiederum ruft die MsgBox-Funktion auf, um eine weitere Meldung anzuzeigen, die den Benutzer darüber informiert, dass das Formular geschlossen wurde. Kompilieren Sie zum Schluss diesen Code, entweder mit VS .NET oder mit folgendem Befehl: vbc /t:winexe /r:system.windows.forms.dll /r:system.drawing.dll listing.3.5.vb
Sie erinnern sich bestimmt noch von Tag 2 daran, dass sich der Namensraum System.ComponentModel in der Assembly System.dll befindet. Das ist der Grund, warum wir hier keine
99
Mit Windows Forms arbeiten
neuen Assemblies referenzieren müssen. Abbildung 3.10 zeigt die Ausgabe, nachdem das Steuerelement zum Schließen des Fensters angeklickt worden ist.
Abbildung 3.10: Das Closing-Ereignis erlaubt Ihnen, das ClosedEreignis abzufangen.
Nicht alle Ereignisse folgen diesem zweistufigen Ablauf. Man findet ihn aber bei einigen Form-Objekten, die Tabelle 3.2 beschreibt. Ereignis
Beschreibung
Closed
Erfolgt beim Schließen eines Formulars
InputLanguage- Erfolgt, sobald der Benutzer die Sprache des Formulars zu ändern versucht Changed Validated
Erfolgt, sobald die Eingabe eines Steuerelements auf Gültigkeit geprüft worden ist
Tabelle 3.2: Ereignisse, die vor dem Cursor-Einsatz stattfinden
Es gibt einige wichtige Ereignisse, von deren Existenz Sie Kenntnis haben sollten, wenn Sie mit Windows Forms zu tun haben. Closed haben Sie ja bereits kennen gelernt: Es wird ausgelöst, sobald sich ein Formular schließt. Es gibt weiter ein Load-Ereignis, das sofort ausgelöst wird, noch bevor ein Formular zum ersten Mal angezeigt wird. Der Ereignishandler für dieses Ereignis ist häufig eine geeignete Stelle, um Komponenten für Ihr Formular zu initialisieren, d.h. falls Sie sie noch nicht im Konstruktor initialisiert haben sollten. Das Activated-Ereignis findet statt, sobald Ihr Formular den Fokus erhält und zur aktiven Anwendung wird – es korrespondiert mit der Activate-Methode. Dementsprechend wird
100
Ereignisbehandlung
Deactivate ausgelöst, sobald Ihr Formular deaktiviert wird, also den Fokus verliert, oder wenn eine andere Anwendung die aktive wird.
Maus- und Tastaturereignisse Maus- und Tastaturaktionen gehören zu den wichtigsten Arten von Ereignissen. Schließlich sind dies wesentliche Formen von Benutzereingaben. Daher gibt es eine ganze Reihe von Ereignissen, die mit diesen zwei Eingabegeräten verbunden sind. Lassen Sie uns mit Tastaturereignissen anfangen. Zuerst an der Reihe ist das KeyPress-Ereignis. Es erfolgt jedes Mal, wenn eine Taste gedrückt wird, ganz gleich welche (wir sehen gleich noch, wie man feststellt, welche Taste es war). Falls dieses Ereignis nicht genügend Kontrollmöglichkeiten eröffnet, gibt es noch die Ereignisse KeyDown und KeyUp, die jeweils ausgelöst werden, sobald eine Taste heruntergedrückt und dann wieder losgelassen wird. Diese Ereignisse liefern zusätzliche Informationen (etwa welche Taste gedrückt wurde), die Sie in Ihrer Anwendung nutzen können. Ihre Ereignishandler, also diejenigen Methoden, die ausgeführt werden, sobald das Ereignis ausgelöst worden ist, sind dementsprechend spezialisiert. In VB .NET erfolgt das Deklarieren dieser Handler auf die gleiche Weise, mit der Sie bereits vertraut sind: 'VB .NET AddHandler Form1.KeyPress, AddressOf methodenName AddHandler Form1.KeyDown, AddressOf methodenName AddHandler Form1.KeyUp, AddressOf methodenName
In C# müssen Sie jedoch die spezialisierten Handler berücksichtigen. Statt EventHandler in Zeile 18 von Listing 3.4 wie gewohnt zu verwenden 18:
btAccept.Click += new EventHandler(this.AcceptIt);
müssen Sie die Objekte KeyPressEventHandler und KeyEventHandler einsetzen: //C# Form1.KeyPress += new KeyPressEventHandler(methodenName) Form1.KeyDown += new KeyEventHandler(methodenName) Form1.KeyUp += new KeyEventHandler(methodenName)
Genau wie das CancelEventArgs-Objekt besitzen auch die Objekte KeyPressEventHandler und KeyEventHandler besondere Eigenschaften, die Ihrer Anwendung helfen festzustellen, welche Aktion das jeweilige Ereignis ausgelöst hat. Der KeyPressEventHandler hat zwei Eigenschaften: Handled und KeyChar. Bei der ersten Methode handelt es sich lediglich um einen true- oder false-Wert, der anzeigt, ob Ihre Methode den Tastendruck verarbeitet hat (ist das nicht der Fall, wird der Tastendruck zur Verarbeitung an das Windows-Betriebssystem geschickt). Meistens werden Sie diese Eigen-
101
Mit Windows Forms arbeiten
schaft auf true setzen, es sei denn, Sie wünschen explizit, dass das Betriebssystem diesen speziellen Tastendruck verarbeitet (wenn Sie etwa die Taste (Druck) nicht handhaben müssen, leiten Sie dies an das Betriebssystem weiter). Die Eigenschaft KeyChar liefert einfach die Taste, die gedrückt wurde. Listing 3.6 zeigt ein Beispiel in VB .NET. Listing 3.6: Verarbeitung von Tastenbetätigungen 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23:
Imports Imports Imports Imports
System System.Windows.Forms System.Drawing System.ComponentModel
Namespace TYWinForms.Day3 public class Listing36 : Inherits Form public sub New() Me.Text = "Tastendruck-Beispiel" AddHandler Me.KeyPress, AddressOf Me.KeyPressed end sub public sub KeyPressed(Sender as Object, e as KeyPressEventArgs) Microsoft.VisualBasic.MsgBox(e.KeyChar) e.Handled = True end sub public shared sub Main() Application.Run(new Listing36) end sub end class End Namespace
In Zeile 11 stoßen Sie auf den Ereignishandler namens KeyPressed, welcher dem Ereignis KeyPress zugeordnet ist. In Zeile 14 deklarieren Sie den Ereignishandler. Beachten Sie bitte, dass er ein KeyPressEventArgs-Objekt als Parameter übernimmt – dieser Name korrespondiert mit dem KeyPressEventHandlerObjekt, das wir weiter oben besprochen haben. In Zeile 15 zeigen Sie einfach das gedrückte Zeichen in einem Meldungsfeld an und setzen dann in Zeile 16 die Handled-Eigenschaft auf true. Abbildung 3.11 zeigt die entsprechende Bildschirmausgabe, wenn das große A gedrückt wurde ((ª) + (a)). Beachten Sie jedoch, dass lediglich Buchstaben, Zahlen und die (¢)-Taste das KeyPressEreignis auslösen. Um andere Tasten wie etwa (Strg), (Alt) und die Funktionstasten (F1) bis (F12) zu handhaben, müssen Sie die KeyUp- und KeyDown-Ereignisse verwenden. Erin-
102
Ereignisbehandlung
nern Sie sich daran, dass diese Ereignisse Handler des Typs KeyEventhandler nutzen, so dass der zweite Parameter Ihrer Ereignishandler-Methode auf KeyEventArgs lauten muss: Public Sub KeyReleased(Sender as Object, e as KeyEventArgs) 'ein wenig Code end Sub
Abbildung 3.11: Solange Ihr Formular den Fokus besitzt, führt jeder Tastendruck die KeyPressed-Methode aus.
Das Objekt KeyEventArgs verfügt über mehrere Eigenschaften, die sich als nützlich erweisen, um festzustellen, welche Taste gedrückt wurde: 쐽
Alt: Wahr- oder Falsch-Wert, der anzeigt, ob die (Alt)-Taste gedrückt wurde
쐽
Control: zeigt an, ob die (Strg)-Taste gedrückt wurde
쐽
Handled: entspricht der Handled-Eigenschaft des Objekts KeyPressEventArgs
쐽
KeyCode: der Tastaturcode für die gedrückte Taste
쐽
KeyData: die Tastendaten für die gedrückte Taste
쐽
KeyValue: der Tastaturcode für die gedrückte Taste
쐽
Modifiers: gibt Flags zurück, die anzeigen, welche Tasten und Zusatztasten (wie etwa (ª), (Strg) oder (Alt)) gedrückt wurden
쐽
Shift: zeigt an, ob die (ª)-Taste gedrückt wurde
Jede Taste auf der Tastatur hat eindeutige KeyCode-, KeyData- und KeyValue-Werte. Die Eigenschaften KeyCode und KeyValue sind üblicherweise gleich. KeyData weist bei den meisten Tasten den gleichen Wert wie die beiden anderen Eigenschaften auf, ist jedoch unter-
103
Mit Windows Forms arbeiten
schiedlich bei Zusatztasten (eine vollständige Referenz finden Sie bei der Keys- bzw. Tasten-Aufzählung in der Dokumentation des .NET Frameworks). Mausereignisse finden in einer standardisierten Reihenfolge statt: 1. MouseEnter: sobald der Mauszeiger über das Formular gelangt 2. MouseMove: wenn der Mauszeiger sich über dem Formular bewegt 3. MouseHover: wenn der Mauszeiger einfach nur über dem Formular »schwebt« (d.h. ohne Bewegung oder Mausklick) 4. MouseDown: wenn man über dem Formular eine Maustaste drückt 5. MouseUp: wenn man die Maustaste wieder löst 6. MouseLeave: wenn der Mauszeiger das Formular wieder verlässt (sich vom Formular wegbewegt) Die Ereignisse MouseEnter, MouseHover und MouseLeave liefern keine besonderen Informationen und verwenden daher den standardmäßigen Ereignishandler EventHandler und die Ereignisparameter-Objekte EventArgs. Die Ereignisse MouseMove, MouseDown und MouseUp liefern hingegen spezifische Informationen und verwenden alle die MouseEventArgsObjekte des Ereignishandlers MouseEventHandler: public Sub MouseClick(Sender as Object, e as MouseEventHandler)
Das MouseEventhandler-Objekt liefert Information wie etwa die genaue Position des Mauszeigers auf dem Bildschirm, welche Maustaste gedrückt wurde usw. Die Eigenschaften sind in Tabelle 3.3 zusammengefasst. Eigenschaft
Beschreibung
Button
Holt die Information ein, welche Maustaste gedrückt wurde (MouseButtons.Left, MouseButtons.Middle, MouseButtons.None, MouseButtons.Right, MouseButtons.XButton1 oder MouseButtons.XButton2)
Clicks
Die Anzahl von Mausklicks (ein Integerwert)
Delta
Die Anzahl von Drehungseinheiten, um die sich das Mausrad bewegt hat
X
Die x-Bildschirmkoordinate des Mauszeigers
Y
Die y-Bildschirmkoordinate des Mauszeigers
Tabelle 3.3: Eigenschaften von MouseEventHandler
104
Ereignisbehandlung
Drag & Drop Das mit Windows eingeführte Merkmal des Drag & Drop, also des Klickziehens, erlaubt es dem Anwender, eine Art Abkürzung zu nehmen, indem er mit der Maus ein Symbol auf dem Bildschirm auf eine Anwendung zieht. Diese übernimmt die Datei, die vom Symbol dargestellt wird, und verarbeitet sie. Haben Sie beispielsweise Microsoft Word geöffnet und ziehen das Symbol eines Word-Dokuments darauf, öffnet Word dieses Dokument automatisch zur Bearbeitung. Klickziehen erlaubt Ihnen auch, nur mit der Maus Dateien aus einem Ordner in einen anderen zu verschieben oder zu kopieren. Es ist recht einfach, eine Windows Forms-Anwendung für Drag & Drop fit zu machen. Als Erstes müssen Sie die Eigenschaft DragDrop des Formulars auf true setzen und dann den Code für die Ereignisse DragDrop, DragEnter, DragLeave oder DragOver schreiben. Die letzten drei Ereignisse sind den ähnlich benannten Mausereignissen sehr ähnlich; sie treten auf, wenn ein Symbol in oder aus Ihrem bzw. über Ihr Formular bewegt wird. Alle diese Ereignisse verwenden den Objekt-Handler DragEventHandler und den Ereignisparameter DragEventArgs. Wir werfen einen Blick auf die Eigenschaften dieses Parameters. Die Eigenschaft AllowedEffects ist ein Indikator, der besagt, welche Drag & DropAktionen stattfinden können. Wenn Sie etwa versuchen, eine Datei, die nur gelesen werden darf, zu ziehen und abzulegen, können Sie diese Datei nur kopieren, nicht aber verschieben. Die Aktionen werden durch die DragDropEffects-Aufzählung angezeigt: DragDropEffects.All, DragDropEffects.Copy, DragDropEffects.Link, DragDropEffects.Move, DragDropEffects.None und DragDropEffects.Scroll. Alle diese Effekte entsprechen einfachen Windows-Funktionen. Die Eigenschaft DragEventsArgs wiederum gibt den sich gerade ereignenden Effekt an. Dieser ist einer der oben aufgeführten DragDropEffects-Werte. Wenn der Benutzer etwa eine Datei zieht, während er die (Strg)-Taste gedrückt hält, wird versucht, eine Kopieroperation durchzuführen, und die Effect-Eigenschaft wird DragDropEffects.Copy anzeigen. Die Data-Eigenschaft enthält das Element, das gerade Gegenstand einer Drag & DropOperation ist. Ungeachtet seines Typs wird dieses Element vom IdataObject-Objekt dargestellt, das zahlreiche unterschiedliche Objekttypen abbilden kann. (Nähere Informationen hierzu finden Sie in der Dokumentation zum .NET Framework.) Die Eigenschaft KeyState verrät Ihnen, ob jeweils die Taste (ª), (Strg) oder (Alt) gedrückt ist, genau wie die Alt-, Control- und Shift-Eigenschaften der KeyUp- und KeyDownEreignisse. Die X- und Y-Eigenschaften schließlich sind die gleichen wie jene für Mausereignisse. Sie zeigen an, an welcher Stelle (im Koordinatensystem) sich zum Zeitpunkt des Ereignisses ein Element befand.
105
Mit Windows Forms arbeiten
Änderungsereignisse Jedes Mal wenn sich eine der Form-Eigenschaften ändert, ist damit normalerweise ein Ereignis verbunden. Wenn man beispielsweise die Text-Eigenschaft des Formulars ändert, wird das TextChanged-Ereignis ausgelöst. Meist werden diese Ereignisarten für Validierungsroutinen verwendet. Wenn Sie etwa die Zahl der in Ihrem Formular verwendbaren Schriftarten begrenzen wollen, könnten Sie einen Handler für das FontChanged-Ereignis erzeugen, der ungültige Benutzerauswahlen überschreibt. Ich werde hier nicht alle diese Ereignisse aufführen. Sie können feststellen, welche das sind, indem Sie einfach das Wort »Changed« (geändert) an die jeweiligen Eigenschaften anhängen, über die Sie heute gelesen haben: TextChanged, CursorChanged, VisibleChanged usw. Alle diese Methoden verwenden Handler vom Typ EventHandler.
3.3
Zusammenfassung
Das Basisobjekt für alle anderen Objekte ist im .NET Framework – nomen est omen – das Object-Objekt. Da es so allgemeingültig gefasst ist, wird es an verschiedenen Stellen verwendet, an denen ein spezialisiertes Objekt entweder nicht gebraucht wird oder sich nicht genauer bestimmen lässt. Object verfügt über fünf Methoden – Equals, ReferenceEquals, GetHashCode, GetType und ToString –, die alle anderen .NET-Objekte erben. Das indirekt von Object abgeleitete Objekt System.Windows.Forms.Form stellt üblicherweise den Hauptbestandteil Ihrer Windows Forms-Anwendungen dar. Es stellt den Rahmen und Hintergrund für weitere Bestandteile der Benutzerschnittstelle bereit und enthält selbst eine reiche Palette an Funktionen. Das Form-Objekt besitzt 101 Eigenschaften, mit denen Sie praktisch jeden einzelnen Aspekt der Benutzeroberfläche steuern können. Sie können Ihr Formular mit Hilfe der Opacity-Eigenschaft transparent machen, es mit der Eigenschaft FormBorderStyle nichtskalierbar machen, und Sie können die Art und Weise der Benutzerinteraktion mit Hilfe der Eigenschaften MaximizeBox, MinimizeBox, HelpButton, SizeGripStyle und ControlBox kontrollieren. Form verfügt zudem über zahlreiche Methoden wie etwa BringToFront oder Focus, die Sie zur Steuerung der Bildschirmanzeige ausführen können.
Sie haben etwas über Ereignisse und die Nachrichtenschleife erfahren, die einer Anwendung das Untätigsein erlaubt, bis ein Benutzer eine Eingabe vornimmt. Das Form-Objekt verfügt über Ereignisse, die anlässlich zahlreicher Benutzeraktivitäten ausgelöst werden, darunter Mausklick, Tastendruck und Drag & Drop.
106
Fragen und Antworten
3.4 F
Fragen und Antworten
Kann ich von einem selbst erstellten Formular erben? Wie werden dabei Eigenschaften behandelt? A
Das ist zutreffend; Sie können von jeder Klasse erben, es sei denn, sie ist mit dem Schlüsselwort NonInheritable (in C# heißt es sealed) deklariert. Der Umgang mit geerbten Eigenschaften ist häufig kompliziert. Schauen Sie sich als Beispiel folgenden Code an: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32:
Imports System Imports System.Windows.Forms Imports System.Drawing Public Class ClassA : Inherits Form private lblMessage as New Label Public Sub New() lblMessage.Location = new Point(100,100) lblMessage.Height = 100 lblMessage.Width = 200 lblMessage.Text = "Hello World!" Me.Controls.Add(lblMessage) End Sub End Class Public Class ClassB : Inherits ClassA private lblMessage as New Label Public Sub New() lblMessage.Location = new Point(100,100) lblMessage.Height = 100 lblMessage.Width = 200 Me.Controls.Add(lblMessage) End Sub End Class Public Class StartForm Public Shared Sub Main() Application.Run(New ClassB) End Sub End Class
Wenn Sie diese Anwendung kompilieren und ausführen, wird das von ClassB definierte Formular angezeigt. Beachten Sie, dass in dieser Klasse nirgends die Text-
107
Mit Windows Forms arbeiten
Eigenschaft des in Zeile 18 deklarierten Bezeichnungsfelds (Label) gesetzt wird. Wenn Sie jedoch die Anwendung ausführen, enthält das Bezeichnungsfeld den Text »Hello World!« Dieser Text wird in Zeile 12 in ClassA festgelegt. All dies passiert nicht deshalb, weil das Bezeichnungsfeld von ClassB von ClassA geerbt worden wäre, sondern vielmehr, weil im Konstruktor von ClassA ein Bezeichnungsfeld mit dem fraglichen Text erstellt und dem Formular hinzugefügt wird. Erinnern Sie sich daran, dass in jedem Konstruktor als Erstes ein Aufruf an den Konstruktor seiner Basisklasse erfolgt. Daher ist zur Ausführungszeit des Konstruktors von ClassB bereits ein Bezeichnungsfeld mit den Worten »Hello World!« vorhanden. Sie können jedoch von ClassB aus dieses Bezeichnungsfeld nicht bearbeiten. Es ist nämlich in Zeile 6 als private deklariert und daher nur für ClassA zugänglich. Würden Sie es in public ändern, erhielten Sie eine Fehlermeldung, denn ClassB hätte das Bezeichnungsfeld lblMessage geerbt und Zeile 18 würde versuchen, es zu redeklarieren – das ist leider keine gültige Operation. Unterm Strich lässt sich also sagen, dass Vorsicht beim Erben eigener Klassen angebracht ist. Achten Sie ebenfalls auf den Kontext der Sicherheit, in dem Variablen deklariert werden.
3.5
Workshop
Dieser Workshop soll Ihnen helfen, sich besser an die heute behandelten Begriffe zu erinnern. Es hilft Ihnen sehr, die Antworten vollständig zu verstehen, bevor Sie weiterlesen. Die Antworten zu den Quizfragen und den Übungen finden Sie in Anhang A.
Quiz Die Fragen 1 bis 3 beziehen sich auf das folgende Codefragment: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11:
108
using System; using System.Windows.Forms; using System.Drawing; public class MyForm : Form { private Label lblMessage = new Label(); public MyForm() { this.Text = "Hello World!"; } }
Workshop
12: 13: 14: 15: 16: 17:
public class StartForm { public static void Main() { Application.Run(new MyForm()); } }
1. Was würde das folgende Statement liefern, wenn man es unter Zeile 9 platzieren würde? Console.Write(this.ToString());
2. Was würde der folgende Code liefern, wenn man ihn unter Zeile 9 platzieren würde? Label lblTemp = new Label(); Console.Write(lblTemp.Equals(this.lblMessage).ToString());
3. Was würde der folgende Code liefern, wenn man ihn unter Zeile 9 platzieren würde? Label lblTemp = new Label(); Console.Write(Object.ReferenceEquals(lblTemp, this.lblMessage).ToString());
4. Wahr oder falsch? Das KeyPress-Ereignis erfordert einen Handler vom Typ KeyEventHandler. 5. Nennen Sie die fünf Eigenschaften des Objekts MouseEventArgs. 6. Schreiben Sie ein einzelnes Statement in VB .NET, das die Breite eines Formulars auf ein Drittel der Bildschirmhöhe festlegt. 7. Welche Eigenschaft kontrolliert, welche Schaltfläche aktiviert wird, sobald der Benutzer die (ESC)-Taste gedrückt hat? 8. Welches ist der Standardwert von FormBorderStyle? 9. Welche drei Ereignisse verwenden das Paradigma, dass mit einer einzelnen Aktion zwei Ereignisse verknüpft sind?
Übungen 1. Erstellen Sie in C# eine Anwendung, die alle sechs Mausereignisse überwacht. Zeigen Sie eine Meldung in einem Textfeld an, wenn die einzelnen Ereignisse auftreten. 2. Erstellen Sie in VB .NET eine Anwendung, mit der die Benutzer die Eigenschaften Text, Height, Width und Opacity durch Werteingaben in eine TextBox und das Drücken einer EINGEBEN-Schaltfläche anpassen können.
109
Menüs und Symbolleisten
4
Menüs und Symbolleisten
Mittlerweile dürften Sie mit dem Form-Objekt und dem Erstellen von Windows FormsAnwendungen vertraut sein. Sie haben bereits gelernt, wie Sie die Benutzeroberfläche verändern, indem Sie einige Steuerelemente wie etwa Label oder TextBox einfügen sowie die Eigenschaften des Formulars verändern. Schauen wir uns einmal einige trickreichere Steuerelemente an. Menüs und Symbolleisten gehören zur Standardausstattung einer Benutzeroberfläche. Praktisch jede Windows-Anwendung weist sie auf, und die Benutzer wissen sie zu bedienen. Darüber hinaus verleihen sie Ihrer Anwendung ein vollständigeres und gepflegteres Aussehen. Dieses Kapitel zeigt, wie Sie diese gebräuchlichen UI-Elemente erstellen und dem Benutzer gestatten, sie zu bedienen. Heute lernen Sie, 쐽
was Windows Forms-Steuerelemente sind,
쐽
wie Sie Ihren Formularen interaktive Menüs hinzufügen,
쐽
wie Sie Ihre Anwendung kontextabhängig machen,
쐽
wie Sie Schaltflächen in eine Symbolleiste einfügen,
쐽
wie man Informationen in der Statusleiste anzeigt,
쐽
wie Sie Ihrer Anwendung Bildlaufleisten hinzufügen.
4.1
Einführung in Windows Forms-Steuerelemente
An den Tagen 1 bis 3 haben Sie mit einigen Steuerelementen gearbeitet, doch Sie haben eigentlich nicht untersucht, was man unter einem Steuerelement versteht. Lassen Sie uns einen Schritt zurücktreten und uns ansehen, was Steuerelemente (Controls) sind und warum und wozu man sie verwendet. Für den Benutzer ist ein Windows Forms-Steuerelement ein selbstständiger Bestandteil der Benutzeroberfläche. Schaltflächen, Menüs und Listenfelder sind allesamt Steuerelemente. Manche davon, wie etwa Schaltflächen, sind interaktiv, während andere, wie etwa LabelSteuerelemente (Bezeichnungsfelder), es nicht sind. Den Benutzer kümmert es nicht, wie ein Steuerelement genau funktioniert – er kann es intuitiv nutzen, um seine Aufgaben zu erledigen. Stellen Sie sich ein Steuerelement wie eine Uhr vor. Der Benutzer sieht nur das Äußere der Uhr und daher nur äußere Eigenschaften, wie etwa die Farbe oder die Uhrzeit. Der Benutzer kann die Standuhr verschieben und Dinge darauf platzieren, denn sie ist eine physische Entität, die sich manipulieren lässt.
112
Einführung in Windows Forms-Steuerelemente
Der Entwickler hingegen betrachtet die Uhr (das Steuerelement) ein wenig anders, da er in der Lage ist, sie zu öffnen und ihr Innenleben zu manipulieren: die Zeit einstellen, sie aufziehen, die Zahnräder verändern, das Pendel in Gang setzen usw. Die »Uhr« steht immer noch für einen Teil der Benutzeroberfläche, doch sie ist auch ein Gegenstand mit Eigenschaften, Methoden und Ereignissen. Ein Steuerelement lässt sich ebenso steuern wie das Form-Objekt. Jedes Steuerelement wird ebenfalls aus einer Klasse erzeugt, die genau jenen Klassen ähnelt, die Sie erstellt haben. Das Steuerelement erbt von anderen Klassen, die sich im .NET Framework befinden. Die Standuhr präsentiert Ihnen ebenso wie Windows Forms-Steuerelemente zwei »Gesichter« (vgl. Abbildung 4.1): ein äußeres für den Benutzer, ein tiefer reichendes inneres für den Entwickler. (Es gibt sogar noch tiefer reichende. Schließlich kann der Hersteller der Zahnräder Elemente kontrollieren, auf die der Entwickler keinen Zugriff hat.) Ihnen ist bekannt, dass Forms, Objects und Steuerelemente Objekte sind, die Sie mit Hilfe ihrer Methoden und Eigenschaften manipulieren können. Wir wollen einmal unser Lieblingsobjekt, Form, mit Formular-Steuerelementen vergleichen. Forms und Steuerelemente sind einander sehr ähnlich. Sie erben beide (direkt oder indirekt) von der Control-Klasse, demzufolge teilen sie zahlreiche Eigenschaften und Methoden miteinander, so etwa ToString, Equals und GetType. Da Sie das Form-Objekt zu
verwenden wissen, kennen Sie die Grundlagen für die Verwendung aller Steuerelemente. Auch Steuerelemente werden im Namensraum System.Windows.Forms gespeichert.
Entwickler kann mit Innenleben arbeiten
Benutzer sieht Äußeres
Abbildung 4.1: Ein Steuerelement stellt ein inneres und ein äußeres Gesicht zur Schau.
Der Unterschied zwischen einem Form und einem Steuerelement besteht darin, dass ein Steuerelement in einer anderen Komponente enthalten sein muss – das ist meistens, aber nicht zwingend ein Form. Steuerelemente bilden den Schwerpunkt der Interaktion des Benutzers, denn normalerweise wird der Hintergrund einer Anwendung (der leere FormBereich) einfach ignoriert. Werfen wir einen kurzen Blick auf die möglichen Typen von Steuerelementen (wir untersuchen sie detaillierter an Tag 6):
113
Menüs und Symbolleisten
쐽
Containersteuerelement e: Eine Reihe von Steuerelementen wurde exklusiv dafür entworfen, andere Steuerelemente aufzunehmen. Steuerelemente wie etwa das PanelSteuerelemente gruppieren Elemente nicht nur, um die Benutzeroberfläche aufzuräumen, sondern um dem Entwickler ihre Bearbeitung zu erleichtern. Fügen Sie etwa einem Panel weitere Steuerelemente hinzu, können Sie deren gemeinsame Eigenschaften mit einem einzigen Befehl ändern, statt für jedes Steuerelement einen Befehl auszugeben.
쐽
Schaltflächen-Steuerelemente: Sie haben bereits mit dem Steuerelement Button gearbeitet. Diese Kategorie umfasst weitere Steuerelemente wie etwa CheckBox und RadioButton, die zwar nicht wie Schaltflächen aussehen mögen, sich aber tatsächlich so verhalten. Eine CheckBox etwa, also ein ankreuzbares Kästchen wie jene in Web-Formularen, lässt sich anklicken, verfügt über eine zugeordnete Textmeldung und kann auf einen Klick hin eine Aktion ausführen – genau wie ein regulärer Button. Diese Steuerelemente gehören zu den gebräuchlichsten in Windows Forms.
쐽
Listen-Steuerelemente: Listen-Steuerelemente werden üblicherweise verwendet, um Daten, meist aus Datenbanken, anzuzeigen. Zu dieser Kategorie gehören Steuerelemente wie die ComboBox und die ListBox. Mit ihnen zu arbeiten, ist nicht einfach, da man dabei mit Sammlungen von Daten arbeitet (im Gegensatz dazu ist es einfach, den Umgang mit ihnen zu erlernen!). Wir überspringen ihre nähere Betrachtung, bis Sie an Tag 9 lernen, wie Sie mit Daten und Datenbanken arbeiten.
쐽
Menü-Steuerelemente: Menüs gehören neben Schaltflächen zu den gebräuchlichsten Steuerelementen in einer Anwendung: von normalen Symbolleisten-Menüs bis hin zu Kontextmenüs. Der Rest dieses Kapitels beschäftigt sich mit der Funktionsweise dieser Steuerelemente.
쐽
Weitere Steuerelemente: Zu den Steuerelementen, die nicht in die obigen Kategorien passen, gehören etwa das Label-Steuerelement, Zeit- und Datum-Steuerelemente sowie das Steuerelement TreeView (Strukturansicht). Das bedeutet nicht, dass sie weniger wichtig wären, im Gegenteil: Wie Sie bereits gesehen haben, ist das Label-Steuerelement eines der gebräuchlichsten überhaupt.
Lassen Sie uns ohne weiteren Verzug lernen, wie man Menüs erstellt!
4.2
Menüs hinzufügen
Wir alle können uns etwas unter Menüs und ihrer Verwendung vorstellen, doch lassen Sie uns ein wenig rekapitulieren. Ein Menü ist eine Gruppe ähnlicher Befehle, die sich in einer direkt unter der Titelleiste befindlichen Leiste befinden. Die Titelzeile des Hauptmenüs dient lediglich der Gruppierung der Kategorien und führt ansonsten keine Funktionen
114
Menüs hinzufügen
aus. Die Unterelemente sind die eigentlichen Arbeitsbienen unter den Menüs. Manchmal können sogar Unterelemente als Menüs dienen, um so weitere Befehle zu Unterkategorien zusammenzufassen. Abbildung 4.2 zeigt ein typisches Menü.
Abbildung 4.2: Das DATEI-Menü des Internet Explorers ist typisch für ein Befehlsmenü.
In Abbildung 4.2 beachten Sie bitte die waagrechten Linien, die Menügruppen im DATEI-Menü voneinander trennen. Diese Zeilen werden Separatoren oder Trennlinien genannt und sie helfen dem Benutzer bei der Navigation in den Anwendungsmenüs. Wie Sie Trennlinien in Ihre Menüs integrieren, lernen Sie im heutigen Abschnitt »Menüs anpassen« weiter unten. In .NET werden die Menükopfzeilen durch das Objekt MainMenu dargestellt, Unterelemente jedoch durch MenuItem-Steuerelemente. Diese ähneln den Ihnen bereits bekannten Steuerelementen, also lassen Sie uns daher in etwas Code eintauchen. Listing 4.1 ist ihr erster Vorstoß ins Menü-Territorium. Listing 4.1: Beispiel für Ihr erstes Menü, geschrieben in VB .NET 1: 2: 3: 4: 5: 6: 7:
Imports System Imports System.Windows.Forms Namespace TYWinForms.Day4 public class Listing41 : Inherits Form private mnuFile as new MainMenu
115
Menüs und Symbolleisten
8: private lblMessage as new Label 9: 10: public sub New() 11: Me.Text = "Listing 4.1" 12: Me.Menu = mnuFile 13: 14: dim miFile as MenuItem = mnuFile.MenuItems.Add("&Datei") 15: miFile.MenuItems.Add(new MenuItem("&Öffnen...")) 16: 17: lblMessage.Text = "Ich liebe WinForms" 18: lblMessage.Width = 200 19: lblMessage.Height = 200 20: 21: Me.Font = new System.Drawing.Font(new System.Drawing.FontFamily("Arial"), 15) 22: Me.Controls.Add(lblMessage) 23: End Sub 24: 25: public Shared Sub Main() 26: Application.Run(new Listing41) 27: end sub 28: end class 29: 30: End Namespace
Ab Zeile 7 erzeugen wir eine Instanz unseres ersten neuen Objekts, dem MainMenu. Dadurch erhalten wir ein Gerüst für das Einfügen weiterer Menüs. Dies und entsprechende Befehle folgen später. In Zeile 12 machen Sie Bekanntschaft mit einer neuen Eigenschaft des Form-Objekts: Menu. Menu muss stets auf ein MainMenu-Objekt zeigen, insbesondere auf das MainMenu des Formulars. Auf diese Weise können Sie so viele Menüs erzeugen, wie Sie wünschen, und dann dasjenige, das angezeigt wird, austauschen – einfach indem Sie die Menu-Eigenschaft bearbeiten. In Zeile 14 sehen Sie die erste Instanz des MenuItem-Objekts. Es lässt sich sehr vielseitig verwenden; es kann jedes Menüelement (MenuItem) darstellen, ganz gleich, ob es sich in der Menühierarchie auf oberster oder drittklassiger Ebene befindet. Sie verwenden die AddMethode des MainMenu-Objekts, um der MenuItems-Auflistung neue Menüelemente hinzuzufügen. Dieses MenuItem wird die oberste Ebene oder Kopfzeile für ein Menü bilden. In diesem Fall haben wir es »Datei« genannt, ein sehr gebräuchliches Menü in WindowsAnwendungen.
116
Menüs hinzufügen
Das kaufmännische Und-Zeichen (&, auch »Ampersand« genannt) im Menünamen bedeutet, dass das Zeichen, das unmittelbar darauf folgt, durch die Tastatur zugänglich ist. Dies wird als Zugriffstaste. In Zeile 14 folgt dem Ampersand der Buchstabe D. So kann der Benutzer das DATEI-Menü mit der Tastenkombination (Alt)+(D) öffnen. Es darf nur eine solche Tastenkombination pro Menüelement geben. Auch nach dem Erstellen eines Menüelements können Sie dessen Titelzeile noch mit Hilfe der Text-Eigenschaft ändern. In Zeile 15 fügen Sie dem in Zeile 14 erzeugten MenuItem-Objekt ein weiteres hinzu. Weil dieses MenuItem zu einem anderen MenuItem-Objekt statt zum MainMenu-Objekt gehört, wird es als Menüelement im Menü statt in der Hauptmenüleiste erscheinen. Das neue MenuItem heißt »Öffnen«. Abbildung 4.3 zeigt das Ergebnis dieses Codes.
Abbildung 4.3: Listing 4.1 erzeugt ein DATEI-Menü mit einem Menüelement namens ÖFFNEN.
Wenn Sie auf das Menüelement klicken, passiert nichts; das liegt daran, dass wir noch einen Handler für dieses Menüelement erstellen müssen. Dafür ändern wir einfach die Zeile 15 wie folgt: miFile.MenuItems.Add(new MenuItem("&Öffnen...", new EventHandler(AddressOf Me.Open_Clicked)))
oder in C# wie folgt: miFile.MenuItems.Add(new MenuItem("&Öffnen...", new EventHandler(this.Öffnen_Clicked)));
Erstellen Sie dann die Öffnen_Clicked-Methode. Fügen Sie den folgenden Code irgendwo zwischen dem Schluss der New-Methode und dem Anfang der Main-Methode ein: public sub Öffnen_Clicked(Sender as Object, e as EventArgs) Microsoft.VisualBasic.MsgBox("Öffnen-Menü angeklickt!") end sub
117
Menüs und Symbolleisten
Statt die vertraute AddHandler-Methode zu verwenden, legen Sie einfach nur die Methode fest, die nach der Menüauswahl ausgeführt werden soll – ein weiterer Parameter bei der Erstellung des Menüelements (wenn Sie wollen, könnten Sie auch AddHandler verwenden). Abbildung 4.4 zeigt das geänderte Ergebnis.
Abbildung 4.4: Das Hinzufügen eines Handlers verleiht den Menüelementen Funktionsfähigkeit.
Lassen Sie uns einen Blick auf ein weiteres Beispiel werfen, diesmal für die Erstellung von Untermenüs. Listing 4.2 zeigt die Anwendung in C#-Code. Listing 4.2: Ein erweitertes Menü in C# 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19:
118
using System; using System.Windows.Forms; namespace TYWinForms.Day4 { public class Listing42 : Form { private MainMenu mnuDatei = new MainMenu(); private Label lblMessage = new Label(); public Listing42() { this.Text = "Listing 4.2"; this.Menu = mnuDatei; MenuItem miDatei = mnuDatei.MenuItems.Add("&Fenster"); miDatei.MenuItems.Add(new MenuItem("&Forms")); miDatei.MenuItems[0].MenuItems.Add(new MenuItem("&Menüs")); miDatei.MenuItems[0].MenuItems[0].MenuItems.Add(new MenuItem("&Sind")); miDatei.MenuItems[0].MenuItems[0].MenuItems.Add(new MenuItem("So")); miDatei.MenuItems[0].MenuItems[0].MenuItems[1].MenuItems.Add(new MenuItem("&Einfach wie"));
Menüs hinzufügen
20: 21: 22: 23: 24: 25: 26: 27: 28: 29:
miDatei.MenuItems[0].MenuItems[0].MenuItems[1].MenuItems.Add(new MenuItem("Kuchenbacken")); } public static void Main(){ Application.Run(new Listing42()); } } }
Bis zur Zeile 14 ist Listing 4.2 mit Listing 4.1 identisch. Ab hier wird es interessant. Die Zeilen 14 und 15 fügen dem MainMenu genau wie zuvor weitere MenuItem-Objekte hinzu. An diesem Punkt haben Sie zwei Hierarchieebenen: die Kopfzeile des Menüs (die wir 'Item A' nennen wollen) und ein einzelnes Menüelement, »Fenster« (das wir 'Item B' nennen wollen). Denken Sie daran, dass die MenuItems-Eigenschaft eine Auflistung ist: Sie können auf jedes Element in der Auflistung mit Hilfe einer Indexzahl verweisen (0 ist stets das erste Mitglied in der Auflistung). In Zeile 16 greifen Sie auf das erste Element in der MenuItems-Auflistung von Item B zu, welches wiederum ein weiteres MenuItem darstellt. Dieses MenuItem wollen wir 'Item C' nennen. Diesem fügen Sie genau wie zuvor ein weiteres MenuItem hinzu. Weil Sie das MenuItem 'Item C' hinzugefügt haben, wird es zu einem weiteren Untermenü. Jetzt verfügen wir über drei Menüebenen: Fenster, Formulare und Menüs. Um auf ein Mitglied in einer Auflistung in VB .NET zuzugreifen, benötigen Sie die Item-Eigenschaft: miDatei.MenuItems.Item(0).MenuItems.Add(new etc etc)
In Zeile 17 erweitern Sie dieses Konzept ein wenig. Sie greifen auf die MenuItems-Auflistung von Item C zu und fügen ein neues MenuItem ein, so dass Sie eine vierte Menüebene erhalten. Zeile 18 tut das Gleiche, indem sie der vierten Ebene ein weiteres MenuItem hinzufügt. Die Zeilen 19 und 20 führen diesen Vorgang fort, indem sie dem zweiten Element (wie durch den Index 1 angezeigt) weitere Untermenüs hinzufügen. Als Ergebnis unserer Bemühungen erhalten wir fünf Menüebenen, wie in Abbildung 4.5 zu sehen ist. Der Vorgang des Verschachtelns von Menüs mag zunächst ein wenig verwirrend erscheinen, aber denken Sie einfach daran, dass das erste MenuItem, das Sie einem anderen MenuItem hinzufügen, stets ein Untermenü erzeugt.
119
Menüs und Symbolleisten
Abbildung 4.5: Das Verschachteln von Menüs erzeugt einen interessanten Quellcode.
Sie können auch Untermenüs dadurch hinzufügen, dass Sie für jedes Element im Menü explizit ein neues MenuItem erzeugen. Schauen Sie sich zum Beispiel folgendes Codefragment an: MenuItem MenuItem MenuItem MenuItem …
miDatei = mnuDatei.MenuItems.Add("&Windows"); miForms = miDatei.MenuItems.Add("&Forms"); miMenüs = miFormulare.MenuItems.Add("&Menüs"); miSind = miMenüs.MenuItems.Add("Sind");
Die erste Zeile erzeugt ganz normal das Element für die Kopfzeile des Menüs. Das nächste Menüelement wird als neues MenuItem erzeugt und der MenuItems-Auflistung der vorhergehenden Ebene hinzugefügt, genau wie Sie das mit der Menükopfzeile gemacht haben. Beachten Sie, dass jede darauffolgende Zeile das neue MenuItem in die MenuItems-Auflistung der vorhergehenden Zeile hinzufügt. Beide Methoden zur Menüerzeugung erledigen diese Aufgabe effizient. Die zweite Methode weist jedoch eine Reihe von Vorteilen auf: Sie erzeugt für jedes Menüelement einen neuen Namen, so dass wir es notfalls später leicht wiederfinden können (statt seine Indexzahl zu benutzen); und der Code ist ebenfalls leichter zu lesen. Letztlich bleibt die Wahl Ihnen überlassen.
Menüs anpassen Menüs sind in höchstem Maß anpassungsfähig. Sie können Menüs zusammenfassen, visuelle Erweiterungen und Kurzbefehle hinzufügen und so weiter. Da diese Verschönerungen dem Endbenutzer eine bessere Bedienungserfahrung vermitteln, wollen wir einen Blick darauf werfen. Die einfachste Methode zur Anpassung Ihrer Menüs besteht darin, ähnliche Elemente zusammenzufassen. Das in Abbildung 4.2 gezeigte DATEI-Menü des Internet Explorers zeigt, wie sich ähnliche Menüelemente gruppieren lassen; eine Trennlinie (Separator)
120
Menüs hinzufügen
separiert eine Serie von Menüelementen von der nächsten. Das Einfügen einer Trennlinie ist problemlos und eine gute Möglichkeit, den Benutzern beim Navigieren durch die Menüs zu helfen. Sehen Sie sich bitte folgendes Stück Code an: MenuItem miDatei = mnuDatei.MenuItems.Add("&Datei"); MenuItem miÖffnen = miDatei.MenuItems.Add("&Öffnen"); MenuItem miSpeichern = miDatei.MenuItems.Add("&Speichern"); miFile.MenuItems.Add("–"); MenuItem miBeenden = miDatei.MenuItems.Add("Beenden");
Die ersten drei Zeilen fügen einem vorhandenen MainMenu-Objekt einfache Menüelemente hinzu. Um eine Trennlinie einzufügen, müssen Sie lediglich die Add-Methode aufrufen und ihr einen Gedankenstrich (–) übergeben. Die CLR versteht: Sie wollen die folgenden Menüelemente zu einer anderen Gruppe zusammenfassen und abtrennen. Diese einfache Erweiterung führt zu dem folgenden Menü in Abbildung 4.6.
Abbildung 4.6: Trennlinien helfen beim Organisieren Ihrer Menüs.
Zusätzlich zu den Zugriffstaten in den Menükopfzeilen können Sie auch Tastenkombinationen für Ihre Menüelemente einbauen. Microsoft Word verwendet beispielsweise (Strg) + (S) als Tastenkombination, um Dokumente zu speichern. Die meisten Anwendungen verwenden die Funktionstaste (F1) als Shortcut zu einem Element des HILFE-Menüs. (Strg) + (O) führt den Menübefehl DATEI/ÖFFNEN aus. MenuItem miÖffnen = miDatei.MenuItems.Add("&Öffnen", this.Öffnen_Clicked, Shortcut.CtrlO);
Der dritte Parameter, den Sie hier sehen, ist die Eigenschaft Shortcut. Sie können die Shortcut-Aufzählung verwenden, um die gewünschte Tastenkombination festzulegen. Diese Aufzählung verfügt über Werte für die meisten Tastenkombinationen, wie etwa Ctrlx wobei x eine beliebige Zahl oder ein beliebiges Zeichen sein kann; in Fx steht x für jede beliebige Zahl, und in CtrlShiftx ist x eine beliebige Zahl oder ein beliebiges Zeichen. In der .NET-Dokumentation erhalten Sie vollständige Informationen dazu.
121
Menüs und Symbolleisten
Wenn Sie während der Erstellung eines Menüelements keine Tastenkombination festlegen wollen, können Sie das auch noch später tun, indem Sie die Eigenschaft Shortcut des MenuItems nutzen: miDatei.Shortcut = Shortcut.ShiftF9
Setzen Sie die ShowShortcut-Eigenschaft auf true, wenn Sie die damit verknüpfte Tastenkombination im Menü neben dem Menüelement anzeigen lassen wollen: MiDatei.Shortcut = ShowShortcut = true
Schließlich gibt es noch ein paar einfache boolesche Eigenschaften, die Sie für jedes Menüelement anpassen können: 쐽
Enabled legt fest, ob der Benutzer das Menüelement auswählen kann oder nicht (deaktivierte Elemente erscheinen grau).
쐽
Visible zeigt an, ob der Benutzer das Element sehen kann (bedenken Sie, dass ein
Menüelement selbst dann noch per Tastenkombination aktiviert werden kann, wenn es nicht sichtbar ist; sorgen Sie dafür, dass sowohl Enabled als auch Visible auf false gesetzt sind, wenn Sie verhindern wollen, dass Benutzer ein Menüelement sehen oder auswählen). 쐽
Checked zeigt an, ob sich neben dem Menüelement ein Häkchen befindet. Das ist
nützlich, wenn Sie beispielsweise kenntlich machen wollen, ob ein Menüelement ausgewählt worden ist. 쐽
DefaultItem zeigt an, ob das Menüelement die voreingestellte Auswahl ist (Standardelemente erscheinen in fetter Schrift).
Um weitere Informationen über MenuItem-Eigenschaften zu erhalten, schlagen Sie bitte in Anhang B nach.
Kontextmenüs Wenn Sie bereits einmal auf ein Formular mit der rechten Maustaste (ein »Rechtsklick«) geklickt haben, wissen Sie, dass dann nichts passiert: kein Popup-Menü öffnet sich, wie so oft in Windows-Anwendungen. Dieses Popup-Menü nach dem Rechtsklick, welches häufig kontext-relevante Menüs zur Auswahl bereitstellt (etwa Ausschneiden, Einfügen usw.), bezeichnet man als Kontextmenü. Wenn Sie bereits einmal etwas länger mit WindowsAnwendungen gearbeitet haben, wissen Sie, dass solch ein Menü für produktives Arbeiten einfach unerlässlich ist. Zum Glück lassen sich Kontextmenüs ebenso einfach einem Formular hinzufügen wie normale Menüs. Alle Kontextmenüs werden durch das ContextMenu-Objekt dargestellt. Es ist dem MainMenuObjekt sehr ähnlich. Menüelemente im ContextMenu werden sogar als MenuItem-Objekte dargestellt. Folglich können Sie alles, was Sie bisher über Menüs gelernt haben, auch auf Kontextmenüs anwenden.
122
Menüs hinzufügen
Am einfachsten lässt sich ein Kontextmenü als Kopie des regulären Menüs anfertigen, das Sie bereits erzeugt haben. Dies können Sie mit der CloneMenu-Methode bewerkstelligen. Der folgende Code erzeugt ein neues ContextMenu-Objekt und fügt die Kopie eines vorhandenen Menüs hinzu: ContextMenu mnuContext = new ContextMenu(); Form1.ContextMenu = mnuContext; MenuItem miContext = miDatei.CloneMenu(); mnuContext.MenuItems.Add(miContext);
Sobald Sie Ihrem Formular diesen Code hinzugefügt haben, wird ein Rechtsklick ein Popup-Menü hervorbringen, das genau Ihrem DATEI-Menü entspricht. Dieses Beispiel ist das allereinfachste Kontextmenü. Der Grund jedoch, warum es »Kontext«-Menü genannt wird, liegt darin, dass die Anwendung feststellen kann, in welchem Teil des Formulars sich der Mauszeiger beim Rechtsklicken befand, und kann so für diese Stelle ein spezialisiertes Menü bereitstellen. So kann etwa ein Rechtsklick auf eine leere Fläche im Formular das DATEI-Menü aufrufen, und ein Rechtsklick auf eine TextBox könnte ein spezielles Kontextmenü mit den Befehlsoptionen AUSSCHNEIDEN und EINFÜGEN darin aufrufen. Um dieses Funktionsmerkmal bereitzustellen, verfügt jedes Windows Forms-Steuerelement (inklusive des Form-Objekts) über die Eigenschaft ContextMenu, die das Menü angibt, welches geöffnet wird, wenn das Steuerelement rechts angeklickt wird. Dies macht es Ihnen recht leicht, Ihre Kontextmenüs anzupassen. Lassen Sie uns mit Listing 4.3 ein Beispiel dafür ansehen. Listing 4.3: Angepasste Kontextmenüs 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18:
using System; using System.Windows.Forms; using System.Drawing; namespace TYWinForms.Day4 { public class Listing43 : Form { private MainMenu mnuFile = new MainMenu(); private ContextMenu ctmTextBox = new ContextMenu(); private ContextMenu ctmLabel = new ContextMenu(); private Label lblMessage = new Label(); private TextBox tbMessage = new TextBox(); public Listing43() { this.Text = "Listing 4.3 "; this.Menu = mnuFile; AddMenus();
123
Menüs und Symbolleisten
19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50:
lblMessage.Text = "Ich liebe Windows Forms "; lblMessage.Location = new Point(100,75); lblMessage.Width = 150; tbMessage.Text = "Ich liebe Windows Forms "; tbMessage.Location = new Point(75,125); tbMessage.Width = 150; this.Controls.Add(lblMessage); this.Controls.Add(tbMessage); } public void AddMenus() { MenuItem miDatei = mnuDatei.MenuItems.Add("&Datei"); MenuItem miÖffnen = miDatei.MenuItems.Add("&Öffnen"); MenuItemSpeichern = miDatei.MenuItems.Add("&Speichern"); miDatei.MenuItems.Add("-"); MenuItem miBeenden = miDatei.MenuItems.Add("Beenden"); MenuItem ctiLabel = ctmLabel.MenuItems.Add("Label-Menü"); lblMessage.ContextMenu = ctmLabel; MenuItem ctiTextBox = ctmTextBox.MenuItems.Add("Textbox-Menü"); tbMessage.ContextMenu =ctmTextBox; } public static void Main() { Application.Run(new Listing43()); } } }
In den Zeilen 1 bis 30 findet sich nichts Neues. Beachten Sie jedoch, dass die Zeilen 9 und 10 Instanzen von ContextMenu-Objekten erzeugen; wir werden sie gleich verwenden. Die Zeile 18 ruft die AddMenus-Methode auf, die in Zeile 32 beginnt. Die Zeilen 33 bis 37 erzeugen ein normales DATEI-Menü für Ihre Anwendung (Sie haben diesen Code bereits in Listing 4.2 gesehen). Zeile 39 erzeugt ein neues MenuItem für das ContextMenu-Objekt ctmLabel. Seine Beschriftung lautet »Label-Menü«. In Zeile 40 verbinden Sie dieses ContextMenu-Objekt (und somit sein untergeordnetes MenuItem-Objekt) mit dem Label-Steuerelement. Dieses Kontextmenü lässt sich nun auf dieses Label anwenden (wenn Sie wollen, könnten Sie dieses Kontextmenü auch anderen Steuerelementen zuweisen).
124
Symbolleisten, Statusleisten und Bildlaufleisten hinzufügen
Die Zeilen 42 und 43 erledigen das Gleiche für das TextBox-Objekt, indem sie ein Menüelement erzeugen, das die Beschriftung »Textbox-Menü« trägt. Da Sie an keiner Stelle auch dem Form-Objekt ein ContextMenu zuweisen, wird ein Rechtsklick auf eine leere Formularfläche kein Kontextmenü öffnen. Dieses Listing erzeugt das in Abbildung 4.7 gezeigte Ergebnis.
Abbildung 4.7: Wenn Sie mit der rechten Maustaste auf das Bezeichnungsfeld (Label) klicken, wird das damit verbundene Kontextmenü angezeigt.
In diesem Beispiel zeigt ein Rechtsklick auf das Textfeld das Kontextmenü »TextboxMenü« an. Wenn Sie einmal die Zeilen 42 und 43 aus dem Listing auskommentieren und es neu kompilieren, zeigt ein Rechtsklick auf das Textfeld ein ganz anderes damit verknüpftes Kontextmenü an: eines, das Befehlsoptionen für Ausschneiden und Einfügen anzeigt. Der Grund dafür ist, dass TextBox (und ebenso RichTextBox) bereits voreingestellte Kontextmenüs besitzen. Indem Sie Ihr eigenes Kontextmenü erzeugen, setzen Sie die vorhandenen Menüs außer Kraft.
4.3
Symbolleisten, Statusleisten und Bildlaufleisten hinzufügen
Symbolleisten, Statusleisten und Bildlaufleisten sind ebenso vertraute Bestandteile jeder Windows-Anwendung wie Menüs. Eine Symbolleiste befindet sich gewöhnlich am oberen Ende eines Anwendungsfensters, also direkt unter der Menüleiste, und enthält Schaltflächen mit Grafiken darauf, die die jeweilige Funktionalität in der Anwendung signalisieren. Word etwa hat eine Schaltfläche mit dem Bild eines geöffneten Ordners, um das Öffnen einer Datei zu bezeichnen, und ein Diskettensymbol für die Speichern-Funktion. Eine Statusleiste befindet sich am unteren Rand des Anwendungsfensters und stellt dem Benutzer Verarbeitungsinformationen zur Verfügung. Ihre einzige interaktive Funktion besteht darin, Meldungen anzuzeigen. Bildlaufleisten schließlich erlauben dem Benutzer, durch ein Formular zu blättern, falls nicht alle Steuerelemente in die vorhandene Fensterfläche passen. Abbildung 4.8 stellt alle drei Leistentypen dar.
125
Menüs und Symbolleisten
Abbildung 4.8: In den meisten Anwendungen finden sich Symbolleisten, Statusleisten und Bildlaufleisten.
Trotz ihrer Namensähnlichkeit erweisen sich die drei Steuerelemente bei der Implementierung als recht unterschiedlich. Die Symbolleiste agiert als selbstständiges Steuerelement (obwohl es weitere Steuerelemente enthält) und ist mit einem ImageList-Objekt verknüpft. Die Statusleiste hingegen agiert ähnlich einem Formular als Container für andere Steuerelemente. Die Bildlaufleiste ist wieder eine andere Angelegenheit und verfügt nur über wenige anpassbare Eigenschaften. Wir betrachten diese Steuerelemente der Reihe nach.
Symbolleisten Eine Symbolleiste besteht aus einer Sammlung von Schaltflächen (normalerweise durch Grafiken dargestellt), die Kurzbefehle (Shortcuts) zu den Funktionen Ihrer Anwendung anbieten. Wahrscheinlich arbeiten Sie bereits seit längerem mit Symbolleisten, so dass wir uns hier nicht bei konzeptionellen Details aufhalten. Die Symbolleiste und ihre Schaltfläche werden durch die Objekte ToolBar und ToolBarButton dargestellt. Diese beiden Objekte ähneln den Objekten MainMenu und MenuItem: Sie erstellen das zweite Objekt und fügen es dem ersten Objekt hinzu, die Schaltflächen also der Leiste. Lassen Sie uns einen Blick auf Listing 4.4 werfen, das Code zeigt, welcher eine einfache Symbolleiste erzeugt.
126
Symbolleisten, Statusleisten und Bildlaufleisten hinzufügen
Listing 4.4: Eine einfache Symbolleiste hinzufügen 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22:
using System; using System.Windows.Forms; namespace TYWinForms.Day4 { public class Listing44 : Form { private ToolBar tlbStandard = new ToolBar(); public Listing44() { ToolBarButton tbbOpen = new ToolBarButton(); tbbOpen.Text = "Öffnen "; tlbStandard.Buttons.Add(tbbOpen); this.Controls.Add(tlbStandard); } public static void Main() { Application.Run(new Listing44()); } } }
In Zeile 7 erzeugen Sie ein neues ToolBar-Objekt auf die gleiche Weise wie ein MainMenu-Objekt. In Zeile 15 fügen Sie es Ihrem Formular wie jedes andere Steuerelement hinzu. In Zeile 10 erstellen Sie ein neues ToolBarButton-Steuerelement: Darauf wird der Benutzer später klicken können. In Zeile 11 setzen Sie die Text-Eigenschaft so, dass das Wort »Öffnen« angezeigt wird (Sie werden gleich merken, dass diese Text-Eigenschaft nicht ganz das ist, wofür Sie sie halten). Schließlich fügen Sie das ToolBarButton-Steuerelement Ihrem ToolBarObjekt hinzu. Ohne diesen Schritt wäre Ihre Symbolleiste leer und würde in sich zusammenfallen. Kompilieren Sie diese Anwendung und führen Sie sie aus. Das Ergebnis sollte wie in Abbildung 4.9 aussehen. Wir haben zwar jetzt eine Symbolleistenschaltfläche, aber einige Dinge scheinen zu fehlen oder nicht zu stimmen. Augenfällig ist, dass sich ihr Text nicht in der Mitte befindet. Zweitens ist mit der Schaltfläche keine Grafik verknüpft. Und schließlich passiert nach einem Klick auf die Schaltfläche überhaupt nichts. Alle diese Mängel lassen sich leicht beheben.
127
Menüs und Symbolleisten
Abbildung 4.9: Listing 4.4 erzeugt eine einfache Symbolleiste mit nur einer Schaltfläche.
Die Text-Eigenschaft sieht nicht nach dem aus, was wir gewohnt sind. Die Text-Eigenschaft auf einem Button-Steuerelement zeigt eine Beschriftung im Zentrum der Schaltfläche. Die Text-Eigenschaft für den ToolBarButton hingegen wird verwendet, um die Grafik auf der Schaltfläche zu ergänzen (denken Sie mal an die ZURÜCK-Schaltfläche im Internet Explorer). Der Grund für das Erscheinen des Textes außerhalb des Zentrums ist also, dass er Platz lässt für die Grafik. Wenn wir der Schaltfläche eine Grafik zuweisen wollen, müssen wir etwas über ein neues Objekt namens ImageList lernen. Es wird meist von anderen Steuerelementen wie etwa dem ToolBar-Steuerelement dazu verwendet, Grafiken mit einer Auflistung (wie etwa ToolBarButtons) zu verknüpfen. ImageList verfügt über Methoden, um die Grafiken, die es enthält, hinzuzufügen, zu entfernen und neu zu sortieren. Nachdem Sie einer ImageList Grafiken hinzugefügt haben, werden die Schaltflächen in einer Symbolleiste auf die Grafiken mit Hilfe ihrer jeweiligen ImageIndex-Eigenschaft verweisen. Etwa so: MyToolBar.ImageList = myImageList; MyToolBarButton.ImageIndex = 1;
Wir wollen eine neue Methode namens AddToolBar verwenden, um die Erstellung unserer Symbolleiste zu bewerkstelligen. Ersetzen Sie die Zeilen 10 bis 13 in Listing 4.4 durch einen Aufruf der Methode AddToolbar();
und fügen Sie dann Ihrer Anwendung den Code, der in Listing 4.5 gezeigt wird, zwischen dem Konstruktor und der Main-Methode hinzu. Listing 4.5: Grafiken mit Symbolleistenschaltflächen verknüpfen 1: 2: 3:
128
private ImageList ilstToolbar =new ImageList(); public void AddToolbar(){
Symbolleisten, Statusleisten und Bildlaufleisten hinzufügen
4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24:
ilstToolbar.Images.Add(Image.FromFile("i_open.bmp ")); ilstToolbar.Images.Add(Image.FromFile("i_maximize.bmp ")); ilstToolbar.Images.Add(Image.FromFile("i_happy.bmp ")); ToolBarButton tbbOpen =new ToolBarButton(); tbbOpen.ImageIndex =0; tbbOpen.Text ="Öffnen "; ToolBarButton tbbMaximize =new ToolBarButton(); tbbMaximize.ImageIndex =1; tbbMaximize.Text ="Maximieren "; ToolBarButton tbbHappy =new ToolBarButton(); tbbHappy.ImageIndex =2; tbbHappy.Text ="Werden Sie glücklich!"; tlbStandard.ImageList =ilstToolbar; tlbStandard.Buttons.Add(tbbOpen); tlbStandard.Buttons.Add(tbbMaximize); tlbStandard.Buttons.Add(tbbHappy); }
Zeile 1 erzeugt ein neues ImageList-Objekt. In den Zeilen 4 bis 6 fügen Sie diesem Objekt Grafiken hinzu, indem Sie die statische FromFile-Methode des Image-Objekts verwenden. (Beachten Sie, dass Sie die Namen der Dateien, die Sie oben sehen, zu solchen ändern sollten, die Sie tatsächlich besitzen!) Diese Methode lädt die angegebene Datei und übergibt sie der Images-Auflistung von ImageList. Da Sie in Listing 4.5 das Image-Objekt verwenden, sollten Sie nicht vergessen, auch den Namensraum System.Drawing zu importieren! Sie könnten etwa folgenden Code am Anfang Ihrer Datei einfügen: using System.Drawing;
Die Zeilen 8 bis 18 erzeugen drei neue ToolBarButton-Objekte, wobei die Eigenschaften für Text und ImageIndex angegeben werden. Der ImageIndex zeigt auf den Index für diejenige Grafik in der ImageList, die Sie mit der Schaltfläche verknüpfen wollen (der erste Index lautet stets 0). In Zeile 20 verknüpfen Sie schließlich die in Zeile 1 erzeugte ImageList mit dem ToolBarObjekt. Dabei verwenden Sie die ImageList-Eigenschaft des Objekts. Die Zeilen 21 bis 23 fügen einfach die neu erstellten Schaltflächen der Symbolleiste hinzu. Abbildung 4.10 zeigt das Ergebnis dieser Datei.
129
Menüs und Symbolleisten
Abbildung 4.10: Jetzt verfügt die Symbolleiste über Schaltflächen mit Symbolen und Beschriftungen.
Die Anwendung sieht schon ein wenig besser gefüllt aus. Aber es gibt noch einiges zu tun: Die Schaltflächen sollten auch eine Funktion erfüllen. Erinnern Sie sich an meine Worte, wonach die ToolBar als ein einzelnes Steuerelement agiert? Gleich werden Sie den Grund dafür erkennen. ToolBarButton-Objekte verfügen über keine Ereignisse, die man behandeln kann (von dem Disposed-Ereignis einmal abgesehen, doch dies wird von einem anderen Objekt geerbt).
Von alleine wissen sie also nicht, dass sie angeklickt wurden. Wenn man also die Aktionen steuern möchte, sobald ein Benutzer auf eine Symbolleistenschaltfläche geklickt hat, muss man sich dem ToolBar-Objekt zuwenden. Das ToolBar-Steuerelement verfügt über ein ButtonClick-Ereignis, das ausgelöst wird, sobald eine der in ihm enthaltenen ToolBarButtons angeklickt wird. Daher müssen alle Ereignisse und Aktionen über die ToolBar erfolgen. Um Ihrer ToolBar einen Handler hinzuzufügen, verwenden Sie folgenden Code: tlbStandard.ButtonClick += new ToolBarButtonClickEventhandler (this.MyToolBarHandler);
Das Listing 4.6 zeigt den MyToolBarHandler. Listing 4.6: Die Behandlung von Symbolleisten-Ereignissen in C# 1: 2: 3: 4: 5: 6: 7: 8: 9: 10:
130
private void MyToolBarHandler(Object Sender, ToolBarButtonClickEventArgs e){ switch(tlbStandard.Buttons.IndexOf(e.Button)){ case 0: //hier folgt etwas Code; break; case 1: // hier folgt etwas Code; break; case 2: // hier folgt etwas Code;
Symbolleisten, Statusleisten und Bildlaufleisten hinzufügen
11: 12: 13:
break; } }
Das Parameterobjekt ToolBarButtonClickEventArgs verfügt über eine Button-Eigenschaft, wie in Zeile 2 zu sehen ist. Sie gibt an, welche Schaltfläche angeklickt wurde. Wir verwenden die IndexOf-Eigenschaft der Buttons-Auflistung, um für jede Schaltfläche eine numerische Darstellung zurückzugeben, die wir für die Entscheidung darüber verwenden können, welcher Code auszuführen ist. Das switch-Statement (in VB .NET wird statt dessen Select Case verwendet) wertet die ihm gelieferte Bedingung gegenüber jedem einzelnen case-Element aus. Wenn die Werte übereinstimmen, wird der Code im betreffenden case-Zweig ausgeführt. IndexOf legt fest, welches Element in der Buttons-Auflistung der ToolBar mit dem im Parameterobjekt angegebenen übereinstimmt und gibt dann den Index des entsprechenden Steuerelements in der Auflistung zurück. Break ist nötig, um der CLR mitzuteilen, nach der Ausführung des case-Statements keine weiteren mehr auszuführen oder auszuwerten. Listing 4.7 zeigt den gleichen Code in VB .NET geschrieben. Listing 4.7: Die Behandlung von Symbolleisten-Ereignissen in VB .NET 1: private Sub MyToolBarHandler(Sender as Object,e as ToolBarButtonClickEventArgs) 2: Select Case tlbStandard.Buttons.IndexOf(e.Button) 3: Case 0 4: 'etwas Code ausführen 5: Case 1 6: 'etwas Code ausführen 7: Case 2 8: 'etwas Code ausführen 9: end Select 10: End Sub
Symbolleisten anpassen ToolBar- und ToolBarButton-Steuerelemente verfügen über einige Eigenschaften, die Ihnen erlauben, deren Aussehen anzupassen. Wir werfen kurz einen Blick darauf, angefangen bei den Eigenschaften des ToolBar-Steuerelements.
Die Eigenschaft Appearance gibt an, ob Ihre Symbolleistenschaltflächen als normale dreidimensionale Objekte (erhabene Schaltflächen) oder flach (Browser-Stil) erscheinen sollen. Den Unterschied versteht man am besten, indem man sich beide Stile ansieht – sie sind beide in Abbildung 4.11 zu sehen.
131
Menüs und Symbolleisten
Abbildung 4.11: Normale Symbolleistenschaltflächen sehen dreidimensional aus; flache Schaltflächen hingegen sind eben das: flach. Appearance lässt sich auf zwei Werte setzen: Normal (3D) oder Flat. Beide Werte gehören zur Aufzählung ToolBarAppearance. Das folgende Codefragment lässt die Schaltfläche flach aussehen: tlb.Standard.Appearance = ToolBarAppearance.Flat;
Funktional gesehen besteht kein Unterschied zwischen den beiden Stilen; es ist lediglich eine Angelegenheit der visuellen Vorliebe. Die AutoSize-Eigenschaft des ToolBar-Steuerelements gibt an, ob sich die ToolBar je nach der Größe der ToolBarButtons, die sie enthält, vergrößern und verkleinern soll. Dementsprechend gibt es auch eine ButtonSize-Eigenschaft, die die Größe der Schaltflächen in der ToolBar vorgibt. Die Standardgröße für eine Schaltfläche beträgt 24 (Breite) x 22 (Höhe) Pixel. Doch wenn ButtonSize nicht anderweitig angegeben ist, passen sich die Schaltflächen der größten Grafik und dem mit ihnen verknüpften Text an. Um die Begrenzung Ihrer Symbolleiste zu steuern, stehen Ihnen die Eigenschaften Divider und BorderStyle zur Verfügung. Divider gibt an, ob eine Trennlinie zwischen der Symbolleiste und anderen Steuerelementen, wie etwa einem Menü, eingezogen werden soll. BorderStyle kann BorderStyle.FixedSingle, BorderStyle.Fixed3D oder BorderStyle.None sein. Diese Eigenschaft funktioniert hier genauso wie für das Form-Objekt. Das Steuerelement ToolBarButton verfügt nur über eine Haupteigenschaft, nämlich Style, und über verschiedene weitere, die diese Eigenschaft unterstützen. Style gibt an, welche Art von Schaltfläche dem Benutzer gezeigt wird. Sie kann einen der Werte in der Aufzählung ToolBarButtonStyle annehmen:
132
Symbolleisten, Statusleisten und Bildlaufleisten hinzufügen
쐽
DropDownButton: Zeigt auf Mausklick ein Dropdown-Menü an. Verwenden Sie die Eigenschaft DropDownMenu, um ein anzuzeigendes MenuItem zuzuweisen. Verwenden Sie die DropDownArrows-Eigenschaft des ToolBar-Objekts, um anzugeben, ob ein Pfeil anzeigt wird, der auf Anklicken hin ein Menü anzeigt.
쐽
PushButton: Die standardmäßige 3D-Schaltfläche, wie sie vom jeweiligen Betriebssystem definiert wird. (Die Schaltfläche kann in manchen Windows-Versionen flach statt dreidimensional erscheinen.)
쐽
Separator: Eine Trennlinie zwischen zwei Schaltflächen.
쐽
ToggleButton: Eine Schaltfläche, die, sobald sie angeklickt wurde, wie eingedrückt aussieht und so bleibt. Verwenden Sie die Pushed-Eigenschaft, um anzugeben, ob die Schaltfläche gerade gedrückt ist. PartialPush ähnelt Pushed, nur dass die Schaltfläche
nicht gedrückt, sondern vielmehr grau eingefärbt ist. Abbildung 4.12 zeigt Beispiele für all diese Stile.
Abbildung 4.12: Hier sind die Schaltflächenstile DropDown, PushButton und ToggleButton umgesetzt.
Statusleisten In einer Windows Forms-Anwendung wird eine Statusleiste durch das StatusBar-Objekt und eine beliebige Anzahl von StatusBarPanels (Statusleistenbereiche) dargestellt. Die StatusBar sieht fast wie ein Label-Steuerelement aus: Sie verfügt über keine Umrisslinien oder Trennlinien. Daher verwendet sie StatusBarPanels, um Informationen abzutrennen und anzuzeigen. Es gibt keine Regel, die besagt, man habe einer StatusBar irgendwelche StatusBarPanels hinzuzufügen – schließlich besitzt die StatusBar eine Text-Eigenschaft, die dem Benutzer Text anzeigen wird –das Standarderscheinungsbild einer Statusleiste sieht allerdings solche Bereiche vor. Die Steuerelemente StatusBar und StatusBarPanel sind den Menü- und SymbolleistenSteuerelementen sehr ähnlich, die Sie heute kennen gelernt haben. Daher dürften Sie sie leicht verstehen können. Wir wollen wieder in etwas Code eintauchen: Listing 4.8 (für dieses Beispiel verwenden wir VB .NET) zeigt, wie eine Statusleiste hinzugefügt wird.
133
Menüs und Symbolleisten
Listing 4.8: Ihrer Anwendung eine in VB .NET geschriebene Statusleiste hinzufügen 1: Imports System 2: Imports System.Windows.Forms 3: 4: Namespace TYWinForms.Day4 5: 6: public class Listing48 : Inherits Form 7: private sbrMain as new StatusBar 8: private lblMessage as new Label 9: 10: public sub New() 11: Me.Text = "Listing 4.8 " 12: 13: AddStatusBar 14: 15: lblMessage.Text = "Ich liebe WinForms" 16: lblMessage.Width = 200 17: lblMessage.Height = 200 18: lblMessage.Font = new System.Drawing.Font(new System.Drawing.FontFamily("Arial "), 15) 19: 20: Me.Controls.Add(lblMessage) 21: Me.Controls.Add(sbrMain) 22: 23: End Sub 24: 25: public sub AddStatusBar() 26: dim sbpName as StatusBarPanel = sbrMain.Panels.Add("Hallo, Dave!") 27: sbpName.Width = 100 28: sbpName.BorderStyle = StatusBarPanelBorderStyle.Raised 29: 30: dim sbpTime as StatusBarPanel = sbrMain.Panels.Add(" ") 31: sbpTime.Text = Datetime.Now.ToString 32: sbpTime.AutoSize = StatusBarPanelAutoSize.Spring 33: sbpTime.Alignment = HorizontalAlignment.Right 34: 35: sbrMain.ShowPanels = true 36: end sub 37: 38: public Shared Sub Main() 39: Application.Run(new Listing48) 40: end sub 41: end class 42: 43: End Namespace
134
Symbolleisten, Statusleisten und Bildlaufleisten hinzufügen
Das Listing demonstriert eine Reihe von Eigenschaften der Steuerelemente StatusBar und StatusBarPanel. In Zeile 7 erzeugen Sie das erste Objekt: die StatusBar. Um den Code irgendwie logisch zu sortieren, haben wir den Initialisierungscode für die Statusleiste in einer Methode namens AddStatusBar untergebracht, die in Zeile 13 aufgerufen wird. Zeile 21 fügt die Statusleiste dem Formular hinzu. Die erste Zeile der AddStatusBar-Methode (Zeile 26) erzeugt ein neues StatusBarPanelObjekt und fügt es der Panels-Auflistung der StatusBar hinzu. Zeile 27 legt die Breite des Bereichs fest und Zeile 28 setzt den BorderStyle auf Raised (einen Wert in der Aufzählung StatusBarPanelBorderStyle; die anderen Werte, die Sie verwenden können, sind None oder Sunken, welches der Standardstil ist). In Zeile 30 wird ein weiterer StatusBarPanel-Bereich erzeugt. Die Text-Eigenschaft ist zunächst auf eine leere Zeichenfolge eingestellt. In Zeile 21 setzen Sie sie mit Hilfe der DateTime.Now-Funktion auf die aktuelle Uhrzeit (verwenden Sie ToString, um die Zeit als Zeichenfolge zurückzugeben – der Standardrückgabetyp ist ein DateTime-Objekt). Zeile 32 setzt die AutoSize-Eigenschaft, welche angibt, wie sich der Bereich vergrößern bzw. verkleinern lässt, je nach seinem Inhalt und/oder der Größe des Formulars. Spring wirkt wie eine Feder, so dass der Bereich den gesamten verfügbaren Raum einnimmt, der noch nicht von anderen Bereichen belegt ist. AutoSize kann auch auf None oder Contents gesetzt sein, wodurch die Größe des Bereichs an seinen Inhalt angepasst wird. In Zeile 33 wird die Alignment-Eigenschaft verwendet, um den Inhalt des Bereichs rechtsbündig auszurichten. Dieser Wert kann Left, Center oder Right sein – die Werte stammen aus der Aufzählung HorizontalAlignment. Zum Schluss setzen Sie in Zeile 35 die ShowPanels-Eigenschaft der StatusBar auf true, damit die von Ihnen erzeugten Bereiche dem Benutzer angezeigt werden. Abbildung 4.13 zeigt, wie diese Anwendung nun aussieht. Sie können Ihrer Statusleiste so viele Bereiche hinzufügen, wie Sie wünschen. Nehmen Sie aber nicht zu viele, sonst kann sie der Benutzer nicht alle sehen. Das Steuerelement StatusBarPanel hat eine weitere wichtige Eigenschaft hinsichtlich seiner Anpassung: Icon. Die Eigenschaft lässt sich dazu nutzen, eine Grafik statt bloß Text in der Statusleiste anzuzeigen (wie zum Beispiel die Statusleiste von Word mit einem Symbol den Status Ihres Dokuments anzeigt). Diese Grafikdatei muss eine .ico-Datei sein. Durch das Hinzufügen des folgenden Codes zur AddStatusBar-Methode in Listing 4.8 erhält man einen neuen Bereich: dim sbpIcon as StausBarPanel = sbrMain.Panels.Add("") sbpIcon.AutoSize = StatusBarPanelAutoSize.Contents sbpIcon.Icon = new Icon("VSProjectApplication.ico")
135
Menüs und Symbolleisten
Abbildung 4.13: Ihre erste Statusleiste enthält eine Meldung sowie die Uhrzeit und das Datum. Event-driven Start application
Enter message loop
Do processing
User input
If user input is “stop” then
Stop
Abbildung 4.14: Die Nachrichtenschleife wartet auf eine Benutzereingabe.
Bildlaufleisten Bildlaufleisten erlauben es Ihnen (und Ihren Benutzern), sich im Formular zu bewegen, wenn die darin enthaltenen Steuerelemente zu groß sind, um in das aktuelle Fenster zu passen. Es gibt zwei Arten von Bildlaufleisten: horizontale (verschieben das Formular zwischen linkem und rechtem Rand), dargestellt durch das HScrollBar-Steuerelement, und vertikale (bewegen das Formular auf und ab, dargestellt durch VScrollBar. Bildlaufleisten lassen sich Ihrem Formular sehr leicht hinzufügen, aber es ist ein wenig schwieriger, sie zum Funktionieren zu bringen. Um zu erfahren, wie Sie Bildlaufleisten verwenden, sehen Sie sich bitte den Code in Listing 4.9 an (eine modifizierte Version von Listing 3.4).
136
Symbolleisten, Statusleisten und Bildlaufleisten hinzufügen
Listing 4.9: Bildlaufleisten implementieren 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44:
using System; using System.Windows.Forms; using System.Drawing; namespace TYWinForms.Day4 { public class Listing49 : Form { Button btAccept = new Button(); Button btCancel = new Button(); Label lblMessage = new Label(); VScrollBar vbarForm = new VScrollBar(); HScrollBar hbarForm = new HScrollBar(); public Listing49() { lblMessage.Location = new Point(75,150); lblMessage.Width = 200; btAccept.Location = new Point(100,25); btAccept.Text = "OK "; btAccept.Click += new EventHandler(this.AcceptIt); btCancel.Location = new Point(100,100); btCancel.Text = "Abbrechen"; btCancel.Click += new EventHandler(this.CancelIt); vbarForm.Dock = DockStyle.Right; vbarForm.Visible = false; hbarForm.Dock = DockStyle.Bottom; hbarForm.Visible = false; this.Resize += new EventHandler(this.Resized); this.AcceptButton = btAccept; this.CancelButton = btCancel; this.Text = "Beispiel für OK- und Abbrechen-Schaltflächen"; this.Height = 200; this.Controls.Add(lblMessage); this.Controls.Add(btAccept); this.Controls.Add(btCancel); this.Controls.Add(vbarForm); this.Controls.Add(hbarForm); } public void Resized(Object Sender, EventArgs e) { if (this.Height < lblMessage.Top + lblMessage.Height) {
137
Menüs und Symbolleisten
45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72:
vbarForm.Visible = true; }else { vbarForm.Visible = false; } if (this.Width < btAccept.Left + btAccept.Width) { hbarForm.Visible = true; }else { hbarForm.Visible = false; } this.Refresh(); } public void AcceptIt(Object Sender, EventArgs e) { lblMessage.Text = "Der OK-Button wurde gedrückt"; } public void CancelIt(Object Sender, EventArgs e) { lblMessage.Text = "Der Abbrechen-Button wurde gedrückt"; } } public class StartForm { public static void Main() { Application.Run(new Listing49()); } } }
Hier gibt es zwar eine Menge Code zu lesen, doch das meiste davon haben wir bereits gestern besprochen. Die Zeilen 15 bis 24 erzeugen und zeigen ein Labelund zwei Schaltflächen-Steuerelemente an. Die Zeilen 32 bis 38 fügen diese Steuerelemente dem Formular hinzu, und die Zeilen 58 bis 65 behandeln die Ereignisse der Schaltflächen. (Mehr Details finden sich nach Listing 3.4.) Die Zeilen 11 und 12 enthalten den ersten neuen Code; sie erzeugen neue VScrollBarund HScrollBar-Steuerelemente. Die Zeilen 26 bis 29 legen die Dock- und Visible-Eigenschaften fest. Da wir wissen, dass das Formular groß genug sein wird, um seine Inhalte komplett anzuzeigen, wollen wir die Bildlaufleisten zunächst einmal unsichtbar lassen. Um die Dock-Eigenschaft kümmern wir uns an Tag 6. Halten wir erst einmal fest, dass unsere Bildlaufleisten am rechten und am unteren Rand unseres Formulars angedockt sind.
138
Symbolleisten, Statusleisten und Bildlaufleisten hinzufügen
In Zeile 31 behandeln Sie ein neues Ereignis des Form-Objekts: Resize. Dieses Ereignis wird natürlich ausgelöst, sobald die Größe des Formulars geändert wird. Zeile 31 besagt: Sobald dies passiert, führe die Methode Resized aus, wie in den Zeilen 43 bis 56 zu sehen. Diese Methode muss zwei Dinge tun: Erstens muss sie bei einer Größenänderung die Größe des Formulars bewerten (waagrecht wie senkrecht) und bestimmen, ob das Formular groß genug zur Darstellung seiner Inhalte ist. Ist das nicht der Fall, zeigt sie die Bildlaufleisten an. Zweitens muss sie die Refresh-Methode aufrufen, um das Formular neu zu zeichnen, so dass die gerade modifizierten Bildlaufleisten gezeichnet oder gelöscht werden können. Die Mindesthöhe des Formulars vor der Anzeige von vertikalen Bildlaufleisten liegt beim unteren Rand des untersten Steuerelements – in unserem Fall ist dies lblMessage. Die Summe der Top- und Height-Eigenschaften des Bezeichnungsfeldes ergibt die richtige Höhe. Das if-Statement in Zeile 44 führt den Vergleich zwischen der Höhe des Formulars und der Mindesthöhe aus. Ist die Formularhöhe niedriger, zeigt man die vertikale Bildlaufleiste an (Zeile 45). Umgekehrt gilt: Ist die Formularhöhe größer als die Mindesthöhe, verbergen Sie die Bildlaufleiste, wie in Zeile 47 zu sehen. Die Zeilen 50 bis 54 machen das Gleiche für die waagrechte Ausrichtung. In diesem Fall entspricht die Mindestbreite der Left-Eigenschaft des am weitesten rechts liegenden Steuerelements plus dessen Breite (Width). In Zeile 55 rufen Sie schließlich die Refresh-Methode auf. Kompilieren Sie diese Anwendung und versuchen Sie das Formular auf eine Größe zu reduzieren, die geringer ist als die minimalen Begrenzungen. Abbildung 4.14 zeigt ein typisches Beispiel.
Abbildung 4.15: Die richtigen Bildlaufleisten tauchen auf, sobald die Formulargröße geändert wird.
Beachten Sie jedoch, dass auf einen Klick auf die Bildlaufleisten hin nichts passiert! Ihr Formular lässt sich nicht wie vorgesehen bewegen. Es gibt zwei Möglichkeiten, diesen Mangel zu beheben. Die erste und einfachere Möglichkeit besteht im Setzen der AutoScoll-Eigenschaft des Formulars auf true: this.AutoScroll = true;
Ihr Formular lässt sich nun wie erwartet bewegen. Die zweite Möglichkeit besteht in einer manuellen Vorgehensweise, wobei man die Ereignisse der Bildlaufleisten behandelt. Normalerweise nutzt man diese Möglichkeit nur dann, wenn man vorher nicht weiß, wie groß ein Formular werden kann (etwa wenn ein Benutzer die Größe dynamisch steigern kann).
139
Menüs und Symbolleisten
Lassen Sie uns untersuchen, wofür der Bildlauf auszuführen ist, wenn die Bildlaufleisten angeklickt werden. Zunächst würde man vermuten, es sei das Form-Objekt, doch das träfe nicht zu. Denken Sie daran, dass die Position des Formulars auf den Desktop bezogen ist; wenn Sie also einen Bildlauf bezüglich des Formular-Objekts versuchen, bewegt es sich nur auf dem Desktop, doch seine Inhalte würden an ihrem relativen Ort (im Formular) verbleiben. Entgegen unseren Erwartungen würden sich die Steuerelemente nicht bewegen. Wir wollen aber die Steuerelemente innerhalb des Formulars bewegen, nicht aber das Formular selbst. Wenn also der Benutzer den Abwärtspfeil auf der senkrechten Bildlaufleiste anklickt, müssen sich alle Steuerelemente nach oben bewegen. Das Entsprechende gilt, wenn der Benutzer in der waagrechten Bildlaufleiste nach rechts klickt: Alle Steuerelemente müssen sich nach links bewegen. Wenn man, wie wir in unserem Beispiel, nur drei Steuerelemente im Formular hat, hält sich der Aufwand in Grenzen. Was ist aber, wenn man mehr hat? Die Neupositionierung jedes einzelnen Steuerelements wird zur Schwerstarbeit. Unser Kniff besteht in der Verwendung eines weiteren Containersteuerelements: ein Panel-Steuerelement, das alle Steuerelemente enthält, die bewegt werden müssen. Dann können Sie das PanelSteuerelement bewegen, und alle seine untergeordneten Steuerelemente werden sich entsprechend mitbewegen! Stellen Sie sich das Panel-Steuerelement als eine Art Mini-Form vor oder als Formular innerhalb eines Form-Objekts. Indem Sie die Steuerelemente in einem Panel-Steuerelement gruppieren, können Sie sie als eine Gruppe manipulieren – das ist genau das, was Sie ja beim Bildlauf erreichen wollen. Das Erzeugen des Panel-Steuerelements ist einfach; fügen Sie den folgenden Code an einer beliebigen Stelle zwischen den Zeilen 8 und 13 in Listing 4.9 ein: Panel pnlForm =new Panel();
Sie wollen sicher dafür sorgen, dass das Panel-Steuerelement die gleiche Größe besitzt wie das Formular, so dass alle Steuerelemente normal angezeigt werden. Fügen Sie dem Konstruktor folgenden Code hinzu: pnlForm.Height =this.Height; pnlForm.Width =this.Width;
Als Nächstes ersetzen Sie die Zeilen 36 bis 38 durch folgenden Code: pnlForm.Controls.Add(lblMessage); pnlForm.Controls.Add(btAccept); pnlForm.Controls.Add(btCancel);
Jetzt sind das Label- und die beiden Button-Steuerelemente nicht mehr mit dem FormObjekt verknüpft, sondern stattdessen mit dem Panel-Steuerelement. Als Nächstes fügen Sie das Panel-Steuerelement dem Konstruktor des Form-Objekts hinzu: this.Controls.Add(pnlForm);
140
Symbolleisten, Statusleisten und Bildlaufleisten hinzufügen
Um die Bildlaufereignisse zu behandeln, müssen Sie im Konstruktor Ereignishandler zum Scroll-Ereignis der Bildlaufleisten hinzufügen, etwa so: vbarForm.Scroll +=new ScrollEventHandler(this.HandleVScroll); hbarForm.Scroll +=new ScrollEventHandler(this.HandleHScroll);
Beachten Sie die Verwendung des Objekts ScrollEventHandler. Dies bedeutet, dass Sie ein Parameterobjekt ScrollEventArgs für Ihre Methoden verwenden. Zum Schluss müssen Sie noch die Methoden HandleVScroll und HandleHScroll erstellen, wie in Listing 4.10 gezeigt: Listing 4.10: Methoden zur Behandlung des Bildlaufs 1: 2: 3: 4: 5: 6: 7:
public void HandleVScroll(Object Sender,ScrollEventArgs e){ pnlForm.Top =0 -e.NewValue; } public void HandleHScroll(Object Sender,ScrollEventArgs e){ pnlForm.Left =0 -e.NewValue; }
Diese zwei Methoden verrichten einfache Aufgaben: Sie bewegen das Panel-Steuerelement auf- oder abwärts und nach links oder rechts, wobei die entsprechenden Top- und Left-Eigenschaften des Panel-Steuerelements verwendet werden. Die Schwierigkeit besteht nun darin, wie man herausfindet, wie weit das Panel-Steuerelement in die jeweilige Richtung bewegt werden soll. Das Panel-Steuerelement sollte stets in Bezug zu seiner Startposition bewegt werden. Wenn die Bildlaufleiste beispielsweise um x Raumeinheiten abwärts bewegt wird, sollte sich das Panel-Steuerelement von seiner ursprünglichen Position aus um x Raumeinheiten aufwärts bewegen. In diesem Fall entspricht die Anfangsposition des Panel-Steuerelements den X-Y-Koordinaten (0,0), was der Grund dafür ist, warum Sie diese Nullen in den Zeilen 2 und 6 sehen. Von dieser Anfangsposition ziehen Sie den Betrag ab, um den sich die Bildlaufleiste bewegt hat. Durch das Subtrahieren stellen Sie sicher, dass sich das Panel-Steuerelement in die entgegengesetzte Richtung des Bildlaufs bewegt (der Benutzer klickt »abwärts«, also bewegen sich die Steuerelemente aufwärts). Tabelle 4.1 zeigt ein paar beispielhafte Situationen. Aktion
Ergebnisse
1. Der Benutzer klickt den AufwärtsPfeil um zehn Einheiten (Wert der Bildlaufleiste = 10)
Das Panel-Steuerelement bewegt sich 10 Einheiten abwärts. Der neue Panel.Top-Wert lautet –10, Panel.Left = 0.
Tabelle 4.1: Beispiele für Bewegungen beim Bildlauf
141
Menüs und Symbolleisten
Aktion
Ergebnisse
2. Der Benutzer verschiebt die Bildlaufleiste bis ganz nach unten (Wert der Bildlaufleiste = Form.Height)
Das Panel-Steuerelement bewegt sich ganz nach oben. Neuer Panel.Top-Wert = 0 – Form.Height.Left = 0. (Beachten Sie, dass sich Bewegungen nicht akkumulieren, da die Bewegung nur relativ zur Ausgangsposition gemessen wird. Das heißt, der Top-Wert in diesem Schritt ist nicht –10– Form.Height.)
3. Der Benutzer verschiebt die BildDas Panel-Steuerelement bewegt sich um 20 Einheiten laufleiste um 20 Einheiten nach links. nach rechts. Top = 0 – Form.Height.Left-Wert = –20. Tabelle 4.1: Beispiele für Bewegungen beim Bildlauf (Forts.)
Und welche Einheiten benutzen nun die Bildlaufleisten? Per Voreinstellung ist die Minimum-Eigenschaft auf 0 gesetzt (dies entspricht der Ausgangsposition der Bildlaufleiste; für die senkrechte Bildlaufleiste also ganz oben, für die waagrechte ganz links). Die Vorgabe für Maximum lautet 100, was bedeutet, dass sich die Bildlaufleisten um 100 Einheiten bewegen können, nicht mehr. Sie können sowohl Minimum als auch Maximum ändern, aber der Effekt ist gleich null: Minimum entspricht immer waagrecht ganz links oder senkrecht ganz oben, und Maximum entspricht dem jeweils entgegengesetzten Ende. Die NewValue-Eigenschaft des ScrollEventArgs-Parameters bestimmt die neue Position der Bildlaufleiste (die der Benutzer durch Verschieben herbeigeführt hat). Gemäß den Zeilen 2 und 6 des Listings 4.10 sind ± 100 Einheiten auf- und abwärts, nach links oder rechts der maximale Bewegungsradius des Panel-Steuerelements. Hat ein Formular kompliziertere Anforderungen an seine Größe, müssen Sie vielleicht den Betrag erhöhen, um den sich ein Panel-Steuerelement bewegen kann. Sollte Ihr Formular also 1000 Pixel hoch sein, dann sind für Ihre vertikale Bildlaufleiste ± 100 Einheiten nicht ausreichend. Somit sollte jede Einser-Einheit der Bildlaufleiste sich um mehr als nur eine Einheit der Höhe des Panel-Steuerelements bewegen. Der folgende Code könnte die notwendige Normalisierung erzielen: int normalizedHeight = pnlForm.Height / vbarForm.Maximum; pnlForm.Top = 0 – (e.NewValue * normalizedHeight);
Wenn Sie die Zeilen 2 und 6 des Listings 4.10 entfernen, erhalten Sie das zuvor besprochene Beispiel, bei dem sich das Formular über den Desktop bewegt.
Formulare sind nicht die einzigen Objekte, die über Bildlaufleisten verfügen können; Sie können beinahe jedem gewünschten Steuerelement Bildlaufleisten hinzufügen, so etwa einer PictureBox oder sogar Schaltflächen. Dies stellt Ihnen vielfältige Entwurfsmöglichkeiten hinsichtlich der Interaktion Ihrer Anwendung mit dem Benutzer zur Verfügung.
142
Symbolleisten, Statusleisten und Bildlaufleisten hinzufügen
Wir wollen noch weitere Eigenschaften betrachten, die Sie im Zusammenhang mit Bildlaufleisten nutzen können. Die Eigenschaften LargeChange und SmallChange lassen sich dazu verwenden, die Anzahl Einheiten, um die sich die Bildlaufleiste nach dem Anklicken bewegt, anzupassen. SmallChange wird meist verwendet, wenn der Benutzer auf einen der Pfeile am Ende der Bildlaufleiste klickt oder eine der (½)- bzw. (¼)-Pfeiltasten drückt. LargeChange wird benutzt, wenn die Bildlaufleiste selbst angeklickt wird oder die (Bild½)und (Bild¼)-Pfeiltasten gedrückt werden. Die Type-Eigenschaft des Parameterobjekts ScrollEventArgs teilt Ihnen mit, was tatsächlich während des Bildlaufereignisses passiert ist, also etwa eine große oder kleine Bewegung oder wohin sich die Bildlaufleiste bewegt hat. Tabelle 4.2 führt diese Werte auf, von denen alle zur Aufzählung ScrollEventType gehören. Wert
Beschreibung
EndScroll
Das Bildlauffeld (Scroll-Box; das Kästchen, das die Position der Bildlaufleiste anzeigt) hat aufgehört sich zu bewegen.
First
Das Bildlauffeld hat sich zum Minimum-Wert bewegt.
LargeDecrement
Das Bildlauffeld hat sich um den LargeChange-Wert nach oben oder nach links bewegt (die Inhalte verschieben sich dann nach unten oder nach rechts).
LargeIncrement
Das Bildlauffeld hat sich um den LargeChange-Wert nach unten oder nach rechts bewegt (die Inhalte verschieben sich dann nach oben oder nach links).
Last
Das Bildlauffeld hat sich zu dem durch Maximum angegebenen Wert bewegt.
SmallDecrement
Das Bildlauffeld hat sich um den SmallChange-Wert nach oben oder nach links bewegt (die Inhalte verschieben sich dann nach unten oder nach rechts).
SmallIncrement
Das Bildlauffeld hat sich um den SmallChange-Wert nach unten oder nach rechts bewegt (die Inhalte verschieben sich dann nach oben oder nach links).
ThumbPosition
Das Bildlauffeld selbst wurde bewegt.
ThumbTrack
Das Bildlauffeld bewegt sich noch.
Tabelle 4.2: Werte für ScrollEventType
Listing 4.11 zeigt ein kurzes Beispiel (zumindest im Hinblick auf die Komplexität) für die Verwendung dieser Werte.
143
Menüs und Symbolleisten
Listing 4.11: So stellt man den Typ des aufgetretenen Bildlaufs fest. 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42:
144
using System; using System.Windows.Forms; using System.Drawing; namespace TYWinForms.Day4 { public class Listing410 : Form { Label lblMessage = new Label(); VScrollBar vbarForm = new VScrollBar(); HScrollBar hbarForm = new HScrollBar(); public Listing410(){ lblMessage.Location = new Point(75,75); lblMessage.Width = 200; vbarForm.Dock = DockStyle.Right; vbarForm.Visible = true; vbarForm.Scroll += new ScrollEventHandler (this.HandleScroll); hbarForm.Dock = DockStyle.Bottom; hbarForm.Visible = true; hbarForm.Scroll += new ScrollEventHandler (this.HandleScroll); this.Text = "Beispiel für ScrollEventType-Wert"; this.Height = 200; this.Controls.Add(lblMessage); this.Controls.Add(vbarForm); this.Controls.Add(hbarForm); } public void HandleScroll(Object Sender, ScrollEventArgs e){ switch(e.Type){ case ScrollEventType.EndScroll: lblMessage.Text = "Die Scroll-Box bewegt sich nicht mehr"; break; case ScrollEventType.First: lblMessage.Text = "Die Scroll-Box befindet sich bei " + ((ScrollBar)Sender).Minimum.ToString(); break; case ScrollEventType.LargeDecrement: lblMessage.Text = "Die Scroll-Box hat sich bewegt –" + ((ScrollBar)Sender).LargeChange.ToString(); break; case ScrollEventType.LargeIncrement: lblMessage.Text = "Die Scroll-Box hat sich bewegt" + ((ScrollBar)Sender).LargeChange.ToString();
Symbolleisten, Statusleisten und Bildlaufleisten hinzufügen
43: 44: 45:
break; case ScrollEventType.Last: lblMessage.Text = "Die Scroll-Box befindet sich bei " + ((ScrollBar)Sender).Maximum.ToString(); break; case ScrollEventType.SmallDecrement: lblMessage.Text = "Die Scroll-Box hat sich bewegt –" + ((ScrollBar)Sender).SmallChange.ToString(); break; case ScrollEventType.SmallIncrement: lblMessage.Text = "Die Scroll-Box hat sich bewegt " + ((ScrollBar)Sender).SmallChange.ToString(); break; case ScrollEventType.ThumbPosition: lblMessage.Text = "Die Scroll-Box hat sich bewegt "; break; case ScrollEventType.ThumbTrack: lblMessage.Text = "Die Scroll-Box bewegt sich"; break; }
46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68:
} } public class StartForm { public static void Main(){ Application.Run(new Listing410()); } } }
Der Konstruktor dürfte recht vertraut aussehen; er erzeugt einfach die Bildlaufleisten und ein Bezeichnungsfeld, danach fügt er die Steuerelemente dem Formular hinzu. Der Scroll-Ereignishandler namens HandleScroll sieht dagegen schon wesentlich interessanter aus. Diese in Zeile 30 beginnende Methode wertet die Type-Eigenschaft des Parameters ScrollEventArgs aus. Mit Hilfe eines switch-Statements vergleicht sie den Type-Wert mit jedem der ScrollEventType-Werte, die in Tabelle 4.2 aufgeführt sind. Für jeden Einzelfall gibt sie eine entsprechende Zeichenfolge an das Label-Steuerelement aus. Zu diesem Listing sind ein paar Bemerkungen angebracht. Erstens müssen Sie die Variablen in den Zeilen 36, 39, 42, 45, 48 und 51 konvertieren. Die Variable Sender ist ein Object-Typ (wie durch die Methodendeklaration in Zeile 30 angezeigt wird). Wir wissen zwei Dinge über dieses Object: Es repräsentiert die angeklickte Bildlaufleiste (warum, verrate ich morgen) und sollte in dieser
145
Menüs und Symbolleisten
Eigenschaft die verschiedenen Bildlaufleisten -Eigenschaften enthalten, darunter Minimum, Maximum usw. Ein Objekt hat jedoch diese Eigenschaft nicht, weshalb Sie es in den richtigen Typ – ScrollBar – umwandeln müssen, damit Sie die richtigen Eigenschaft erreichen können. (Mehr dazu morgen.) Als Zweites ist festzustellen, dass Sie in ein ScrollBar- und nicht in ein VScrollBaroder HScrollBar-Steuerelement umwandeln müssen (die beiden Letzteren erben ihre Eigenschaften vom ScrollBar-Steuerelement). Daher ist es unerheblich, ob das angeklickte Steuerelement nun ein VScrollBar- oder HScrollbar-Steuerelement ist, denn alles, was wir benötigen, ist der Zugang zu den entsprechenden Eigenschaften – welche die ScrollBar-Klasse bereitstellt. (Mehr dazu morgen.) Abbildung 4.15 zeigt dieses Beispiel, nachdem die Bildlaufleiste unterhalb des Bildlauffeldes angeklickt wurde.
Abbildung 4.16: Wenn man unterhalb des Bildlauffeldes klickt, wird ein Bildlauf um den LargeChange-Wert ausgeführt.
4.4
Zusammenfassung
Sie haben heute etwas über drei wichtige und häufig eingesetzte Arten von Steuerelementen gelernt: Menüs, Symbolleisten und Bildlaufleisten. Hinsichtlich ihrer Funktion sind sie sehr ähnlich und teilen sich sogar viele ähnliche Eigenschaften, doch hinsichtlich ihrer Implementierung sind sie völlig verschieden voneinander. Ein Steuerelement in Windows Forms ist ein Objekt, das dem Benutzer eine Schnittstelle präsentiert, mit der häufig eine Interaktion möglich ist. Es gibt unterschiedliche Arten von Steuerelementen, darunter Containersteuerelemente, Menüs und Schaltflächen (Buttons). Menüs lassen sich durch die Steuerelemente MainMenu und MenuItem darstellen. Das MainMenu nimmt Menüelemente (MenuItems) auf, welche wiederum die Beschriftungen präsentieren, die der Benutzer sieht und mit denen er in Menüs interagiert. Kontextmenüs, die sich öffnen, wenn der Benutzer auf ein Steuerelement rechtsklickt, verwenden ebenfalls MenuItem-Steuerelemente, setzen aber MainMenu anstelle von ContextMenu ein.
146
Fragen und Antworten
Eine Symbolleiste ähnelt einem Menü, nur dass man ein ToolBar-Steuerelement als übergeordnetes Objekt einsetzt und die ToolBarButton-Steuerelemente als untergeordnete Steuerelemente (ToolBarButtons können sogar MenuItem-Steuerelemente als untergeordnete Steuerelemente haben, wie es bei Dropdown-Schaltflächen der Fall ist). Von den beiden kann nur das ToolBar-Steuerelement verwendbare Ereignisse haben, so dass jeder Klick auf eine Schaltfläche durch seinen Ereignishandler zu behandeln ist – üblicherweise mit einem switch- oder Select Case-Statement. Bei der Statusleiste handelt es um eine weitere Containerart. Sie verwendet die Steuerelemente StatusBar und StatusBarPanel. In der Regel sind Statusleisten nicht interaktiv, sondern zeigen lediglich Informationen an. Sowohl StatusBar als auch StatusBarPanel verfügen über Eigenschaften, die sich zur Informationsanzeige nutzen lassen, doch das normale Vorgehen sieht so aus, dass man nur das StatusBarPanel-Steuerelement als Anzeigemechanismus verwendet und das Steuerelement StatusBar als Container. Bildlaufleisten schließlich gibt es in zwei Varianten: HScrollBar und VScrollBar stellen waagrechte bzw. senkrechte Bildlaufleisten dar. Sie haben erfahren, dass es recht leicht ist, sie Ihrem Formular hinzuzufügen, dass jedoch die Behandlung ihrer Ereignisse eine sorgfältige Planung erfordert – insbesondere dann, wenn man herauszufinden versucht, für welche Elemente, um wie viele Einheiten und in welche Richtung der Bildlauf stattfinden soll. Beide Steuerelemente erben von der Basisklasse ScrollBar.
4.5 F
Gibt es ein Ereignis, das signalisiert, dass sich gleich ein Menü öffnen wird? A
F
Fragen und Antworten Ja, es gibt ein solches Ereignis: PopUp. Wenn Sie eine Aktion direkt vor dem Eintreten dieses Ereignisses ausführen lassen wollen – etwa das Verbergen oder Anzeigen von Menüelementen –können Sie für das Ereignis PopUp einen Handler erstellen.
Inwiefern unterscheidet sich die NewValue-Eigenschaft des ScrollEventArgs-Objekts von der Value-Eigenschaft? A
NewValue gibt den Wert an, um den sich die Bildlaufleiste während eines Ereignisses ändert. Value hingegen gibt den statischen Wert an. Häufig sind diese Werte identisch, manchmal aber auch nicht.
Wenn beispielsweise die Bildlaufleiste oder die Pfeile angeklickt werden, sind diese Werte identisch. Wenn jedoch das Bildlauffeld selbst mit der Maus bewegt wird, dann ist Value der vorhergehende Wert, während NewValue der aktuellen Position entspricht. Sobald das Bildlauffeld wieder losgelassen wird, nehmen beide Werte wieder den gleichen Wert an.
147
Menüs und Symbolleisten
4.6
Workshop
Dieser Workshop soll Ihnen helfen, sich besser an die heute behandelten Begriffe zu erinnern. Es hilft Ihnen sehr, die Antworten vollständig zu verstehen, bevor Sie weiterlesen. Die Antworten zu den Quizfragen und den Übungen finden Sie in Anhang A.
Quiz 1. Wahr oder falsch? Alle Steuerelemente inklusive des Form-Objekts erben von der Klasse Control. 2. Welches Objekt muss mit Symbolleisten verknüpft sein, damit Grafiken in den Symbolleistenschaltflächen angezeigt werden? 3. Wie heißen die drei optionalen Parameter für einen MenuItem-Konstruktor, und welches sind ihre Typen? 4. Welches Zeichen wird verwendet, um eine Tastenkombination für einen Buchstaben in der Beschriftung eines Menüelements bereitzustellen? 5. Schreiben Sie eine Zeile C#-Code, die ein ToolBarButton-Steuerelement namens MyFirstButton anweist, die vierte Grafik in der zugeordneten ImageList zu verwenden. 6. Wahr oder falsch? Das Ereignis, das für die Behandlung von Mausklicks auf Symbolleistenschaltflächen verwendet wird, heißt Click. 7. Welche sind die Standardwerte für die Eigenschaften Minimum, Maximum, SmallChange und LargeChange einer Bildlaufleiste?
Übung Erstellen Sie in VB .NET eine Anwendung, die ein personalisiertes Menü verwendet wie jene, die mit Microsoft Office 2000 und Windows 2000 eingeführt wurden. Diese Menüs zeigen nur die zuletzt benutzten Menüelemente, während sie die anderen verbergen und es dem Benutzer gestatten, auf einen Pfeil zu klicken, um weniger häufig ausgewählte Menüelemente anzuzeigen. Ihre Anwendung sollte ein Menü anbieten, das verborgene Menüelemente besitzt. Wird ein bestimmtes Menüelement angeklickt, sollen die verborgenen Elemente angezeigt werden. Kümmern Sie sich nicht um das Hinzufügen von Ereignishandlern für jedes Menüelement.
148
Workshop
Ihr Menü kann recht einfach sein: Es braucht sich nicht daran zu erinnern, welche Menüelemente der Benutzer am häufigsten auswählt, und es wird keine verborgenen Menüelemente anzeigen, nur weil der Mauspfeil über dem Menü schwebt. Sobald der Benutzer auf die Schaltfläche WEITERE... (More) klickt, muss er das Menü erneut öffnen, um die bislang verborgenen Elemente sehen zu können. (Sie werden sehen, wie man fortgeschrittenere Funktionen verwenden kann, wenn Sie an Tag 13 etwas über GDI+ erfahren.
149
Ereignisse in Windows Forms
5
Ereignisse in Windows Forms
Einer der wichtigsten Aspekte an einer Anwendung ist sicherlich – von der visuellen Oberfläche abgesehen – die Art und Weise, wie sie mit dem Benutzer interagiert. Wie Sie bereits wissen, wird diese Interaktion durch Ereignisse und deren Handler realisiert. Ein Ereignis ist das Resultat einer Aktion, also einer Meldung, die erzeugt wurde, um das Auftreten einer Aktion zu signalisieren. Die Aktion kann vom Benutzer oder von der Anwendung selbst verursacht worden sein. Ein Ereignishandler ist jene Methode, die reagiert, sobald ein Ereignis auftritt. Heute befassen wir uns mit Ereignissen und ihren Methoden, was sie für Ihre Anwendung bedeuten und warum man sie benötigt. Ich werde Ihnen eine neue Art von Klasse namens Delegate vorstellen, die Ihrer Anwendung bei der Ereignisbehandlung hilft. Heute lernen Sie, 쐽
was man in technischer Hinsicht unter einem Ereignishandler versteht,
쐽
wie Sie Ihre Ereignisse miteinander »verdrahten«,
쐽
wie Sie Delegaten verwenden,
쐽
wie Sie Ihre eigenen Ereignisse und Ereignisparameter erzeugen,
쐽
was jeder Parameter eines Ereignishandlers bewirkt, woher er stammt und wie man ihn einsetzt.
5.1
Was sind Ereignishandler?
Aus den vorausgegangenen Kapiteln verfügen Sie bereits über eine gewisse Erfahrung im Umgang mit Ereignissen. Sie wissen, was ein Ereignis ist und ungefähr, was ein Ereignishandler ist. Nun geht es um Letzteren. Um Ihre Anwendung zu einer Reaktion auf ein Ereignis zu veranlassen, müssen Sie einen Ereignishandler erstellen. Dieses Stück Code wird nur dann ausgeführt, wenn ein Ereignis eintritt. Wenn Sie beispielsweise Ihr Auto starten wollen, muss zwischen dem Gaspedal, dem Motor und dem Benzintank eine gewisse Verbindung bestehen, sonst erfolgt keine Aktion: es ist eine Verknüpfung nötig. Das Gleiche lässt sich für Ereignisse in .NET sagen. Eine Aktion mag erfolgen und ein Ereignis passieren, doch sofern es keine Verbindung dazwischen gibt, wird nichts stattfinden. Dementsprechend nennt man den Vorgang der Erstellung eines Ereignishandlers für ein Ereignis auch die Verknüpfung eines Ereignisses.
152
Was sind Ereignishandler?
Ereignisse behandeln Wir wollen uns zunächst eine Anwendung ansehen, die einige aus Menüelementen erzeugte Ereignisse verarbeitet, wie in Listing 5.1 zu sehen. Listing 5.1: Mit Ereignishandlern arbeiten 1: Imports System 2: Imports System.Windows.Forms 3: Imports System.Drawing 4: 5: Namespace TYWinForms.Day5 6: 7: public class Listing51 : Inherits Form 8: private mnuFont as new MainMenu 9: private lblMessage as new Label 10: 11: public sub New() 12: Me.Text = "Listing 5.1 " 13: Me.Menu = mnuFont 14: 15: lblMessage.Text = "Testen von Ereignissen" 16: lblMessage.Location = new Point(75,75) 17: lblMessage.Height = 50 18: lblMessage.Width = 150 19: lblMessage.BackColor = Color.LightBlue 20: 21: dim miFont as MenuItem = mnuFont.MenuItems.Add("Font") 22: dim miTimes as MenuItem = miFont.MenuItems.Add("Times Roman ") 23: AddHandler miTimes.Click,new EventHandler(AddressOf Me.TimesClicked) 24: 25: dim miArial as MenuItem = miFont.MenuItems.Add("Arial") 26: AddHandler miArial.Click, new EventHandler(AddressOf Me.ArialClicked) 27: 28: dim miWing as MenuItem = miFont.MenuItems.Add("Wingdings") 29: AddHandler miWing.Click, new EventHandler(AddressOf Me.WingClicked) 30: 31: Me.Controls.Add(lblMessage) 32: End Sub 33: 34: public sub TimesClicked(Sender as Object, e as EventArgs) 35: lblMessage.Font = new Font("Times",15) 36: mnuFont.MenuItems(0).MenuItems(0).Checked = True
153
Ereignisse in Windows Forms
37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60:
mnuFont.MenuItems(0).MenuItems(1).Checked = False mnuFont.MenuItems(0).MenuItems(2).Checked = False end sub public sub ArialClicked(Sender as Object, e as lblMessage.Font = new Font("Arial", 15) mnuFont.MenuItems(0).MenuItems(0).Checked = mnuFont.MenuItems(0).MenuItems(1).Checked = mnuFont.MenuItems(0).MenuItems(2).Checked = end sub
EventArgs) False True False
public sub WingClicked(Sender as Object, e as EventArgs) lblMessage.Font = new Font("Wingdings",15) mnuFont.MenuItems(0).MenuItems(0).Checked = False mnuFont.MenuItems(0).MenuItems(1).Checked = False mnuFont.MenuItems(0).MenuItems(2).Checked = True end sub public Shared Sub Main() Application.Run(new Listing51) end sub end class End Namespace
Diese Anwendung zeigt etwas Text an und erlaubt dem Benutzer, die Schriftart – den Font – mit Hilfe eines Menüs zu ändern. Zunächst einmal die Grundelemente: Die Zeilen 1 bis 3 importieren die zu verwendenden Namensräume. In Zeile 8 und 9 werden die beiden Hauptsteuerelemente der Anwendung deklariert: ein MainMenu und ein Label. In Zeile 11 beginnt der Konstruktor für die Windows Forms-Klasse. Die Zeilen 15 bis 19 initialisieren die Eigenschaft des Label-Steuerelements, wobei sie seine Größe, Position und Text-Eigenschaften festlegen. Der nächste Codeabschnitt dürfte noch von gestern vertraut sein. Zeile 21 fügt dem MainMenu-Objekt ein Menüelement mit der Aufschrift »Font« hinzu. Dieses erste Menüelement wird die Kopfzeile des ganzen Menüs. Zeile 22 fügt dieser Kopfzeile ein Menüelement hinzu, nämlich die erste unserer zur Auswahl stehenden Schriftarten. Zeile 23 verwendet die AddHandler-Methode, um dem Click-Ereignis des Menüelements einen Ereignishandler hinzuzufügen. Das Click-Ereignis wird stets ausgelöst, wenn das zugehörige Menüelement angeklickt wird. Diese Zeile benachrichtigt die Anwendung von einem Ereignishandler namens TimesClicked, der für dieses Ereignis zu verwenden ist (mehr dazu heute im Abschnitt »Delegaten«). Die Zeilen 25 bis 29 erledigen die gleiche Aufgabe wie die Zeilen 22 und 23, nämlich weitere Menüelemente für weitere Fontoptionen zu erstellen (Arial und Wingdings) und den
154
Was sind Ereignishandler?
Click-Ereignissen dieser Menüelemente Ereignishandler zuzuweisen. Diese Zeilen ver-
knüpfen also die Ereignisse. Sobald eines dieser Menüelemente angeklickt wird, beginnt die Ausführung der jeweiligen Methode (TimesClicked, ArialClicked oder WingClicked). Dieser Ablauf ist recht simpel, obwohl der Code vielleicht ein wenig seltsam aussehen mag. Die TimesClicked-Methode in Zeile 34 ändert die Schriftart des Label-Steuerelements in Zeile 35. An Tag 3 haben wir untersucht, wie man Schriftarten mit Hilfe der Objekte Font und FontFamily ändert. Heute lernen wir eine neue Vorgehensweise. Statt das FontFamilyObjekt einzusetzen, übergeben wir einfach eine Zeichenfolge mit der Schriftart, zu der wir wechseln möchten – in diesem Fall Times. Der zweite Parameter ist wieder die anzuzeigende Schriftgröße. Um den Benutzer wissen zu lassen, welche Schriftart gerade benutzt wird, wollen wir die Menüelemente miTimes, miArial oder miWing mit Häkchen versehen, sobald sie jeweils ausgewählt sind. Die folgende Zeile bedeutet, dass das erste MenuItem, das dem MainMenuObjekt mnuFont hinzugefügt wurde, ein Häkchen erhalten soll – anders ausgedrückt, das miTimes-Menüelement: mnuFont.MenuItes(0).MenuItems(0).Checked = True
Die nächsten beiden Zeilen (37 und 38) verweisen auf die Elemente miArial und miWing (die zweiten und dritten Menüelemente, die nach dem ersten MenuItem dem MainMenuObjekt mnuFont hinzugefügt wurden). Da diese Optionen offensichtlich gerade nicht ausgewählt sind, sollten sie kein Häkchen aufweisen und ihre Checked-Werte auf False lauten: mnuFont.MenuItes(0).MenuItems(1).Checked = False mnuFont.MenuItes(0).MenuItems(2).Checked = False
Da Sie nicht wissen, welches dieser Menüelemente vorher mit Häkchen versehen war, setzen Sie beide auf False, um sicher zu gehen. Die ArialClicked- und WingClicked-Methoden in Zeile 41 bzw. 48 machen das Gleiche wie die TimesClicked-Methode, nur dass sie natürlich die Arial- bzw. Wingdings-Schriftarten anstelle von Times Roman verwenden. Zeile 55 schließlich deklariert den Eintrittspunkt zur Anwendung, nämlich die MainMethode. Sie ruft einfach die Run-Methode auf. Abbildung 5.1 zeigt diese Anwendung in Aktion. Lassen Sie uns kurz zur TimesClicked-Methode zurückkehren. Bitte beachten Sie die Signatur dieses Ereignishandlers: public sub TimesClicked(Sender as Object, e as EventArgs)
Es handelt sich hierbei um die Standardsignatur eines Ereignishandlers (anders ausgedrückt: seine Deklaration). Alle Ereignishandler weisen beinahe identische Signaturen auf. Es ändert sich jeweils nur der Methodenname (hier also TimesClicked) und in seltenen Fällen auch der abschließende Parametertyp EventArgs (Näheres dazu im Abschnitt »Delegaten«).
155
Ereignisse in Windows Forms
Abbildung 5.1: Die Schriftart ändert sich als Reaktion auf Ereignisse.
Der erste Parameter, Sender, stellt das das Ereignis erzeugende Objekt dar. In Listing 5.1 wäre dies das MenuItem-Objekt miTimes. Der zweite Parameter enthält jede nähere Information über das Ereignis. Diese Parameter werden eingesetzt, um den Ereignishandler bei seiner Aufgabe zu unterstützen. Meistens werden Sie diese Standardsignatur für alle Ihre Ereignishandler benutzen, wodurch sie nicht nur sehr leicht zu schreiben, sondern auch leicht aus bereits geschriebenem Code herauszugreifen sind. Dies war ein einfaches Beispiel, wie man Ereignishandler verwenden kann. Wir haben pro Ereignis, das uns interessierte, einen Ereignishandler erzeugt, sozusagen einen Draht pro Ereignis. Für alle anderen Ereignisse erzeugen wir keine Handler, wodurch diese praktisch ignoriert werden.
Mehrfache Ereignisse behandeln Ein sehr interessantes .NET-Merkmal besteht darin, dass ein Ereignis nicht unbedingt auch seinen eigenen Ereignishandler haben muss: Die Ereignisse können Handler gemeinsam nutzen! Eine Verknüpfung kann von einem Handler zu mehreren Ereignissen bestehen. Übertragen auf unseren Auto-Vergleich, hieße das, dass sich der Wagen auf mehrere Arten in Bewegung versetzen lässt, so etwa durch Anschieben oder durch das Loslassen der Bremse an einem abschüssigen Hügel. Da aber so viele Ereignisse einen Ereignishandler auslösen können, ist es umso wichtiger herauszufinden, welches Objekt das Ereignis erzeugt hat. An diesem Punkt kommt der Einsatz der Parameter eines Ereignishandlers unseren Wünschen entgegen. Listing 5.2 zeigt ein Beispiel eines mehrere Ereignisse verarbeitenden Ereignishandlers. Fertigen Sie von Listing 5.1 eine Kopie an und ersetzen Sie die Zeilen 34 bis 53 durch den Code in diesem Listing.
156
Was sind Ereignishandler?
Listing 5.2: Die Behandlung mehrerer Ereignisse mit nur einem Handler 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16:
public sub FontClicked(Sender as Object, e as EventArgs) dim item as MenuItem if Sender is mnuFont.MenuItems(0).MenuItems(0)then lblMessage.Font = new Font("Times ", 15) elseif Sender is mnuFont.MenuItems(0).MenuItems(1)then lblMessage.Font = new Font("Arial ", 15) elseif Sender is mnuFont.MenuItems(0).MenuItems(2)then lblMessage.Font = new Font("Wingdings ", 15) end if for each item in mnuFont.MenuItems(0).MenuItems item.Checked = False next item CType(Sender,MenuItem).Checked = True end sub
Die FontClicked-Methode ersetzt die Methoden TimesClicked, ArialClicked und WingClicked aus Listing 5.1 – sie erledigt die gleiche Aufgabe, doch auf etwas andere Weise. Beachten Sie die Signatur in Zeile 1: Sie ist immer noch die gleiche Standardsignatur. In Listing 5.1 verwendeten wir keinen dieser Parameter, doch diesmal verwenden wir sie, um zu bestimmen, welches Objekt das Ereignis erzeugt hat. In Zeile 2 deklarieren Sie ein neues MenuItem-Objekt; wir werden es gleich einsetzen. Die Zeilen 4 bis 10 stellen fest, welches Menüelement das Ereignis erzeugt hat, auf das diese Methode reagiert. Zu der Reaktion gehört die Auswertung des Sender-Parameters und dessen Vergleichen mit jedem der MenuItem-Objekte, die in der Auflistung miFont.MenuItems enthalten sind. Denken Sie daran, dass Sender das Objekt darstellt, das das Ereignis erzeugt hat. Die Schriftart im Label-Steuerelement ändert sich je nach dem Wert von Sender. Wir verwenden kein Gleichheitszeichen, um den Vergleich in den Zeilen 4, 6 und 8 zu bewerten, denn der Gleichheitsoperator funktioniert nur bei einfachen Datentypen wie etwa Zahlen oder Zeichenfolgen. Bei Objekten benötigen wir hingegen das Schlüsselwort is. Im nächsten Schritt verleihen wir dem aktuellen Menüelement ein Häkchen und entfernen die Häkchen bei den restlichen Menüelementen. Zu diesem Zweck verwenden wir eine for each-Schleife wie in Zeile 12. Sie nimmt jedes MenuItem aus der Auflistung miFont.MenuItems, weist es der Variablen item, die wir in Zeile 2 erzeugten, zu und setzt die Eigenschaft Checked dieser Variablen auf False, so dass die Häkchen aller anderen
157
Ereignisse in Windows Forms
MenuItems entfernt werden. Die for each-Schleife hätte man auch durch eine einfache forSchleife bewerkstelligen können, etwa so: for i = 0 to mnuFont.MenuItems(0).MenuItems.Count – 1 mnuFont.MenuItems(0).MenuItems(i).Checked = False next i
Die for-Schleife setzt eine Variable i ein, um durch die Gesamtzahl aller Menüelemente zu iterieren. Vergessen Sie nicht, die Variable i zu deklarieren, falls Sie diese Methode verwenden. Da nun alle MenuItems häkchenlos sind, brauchen wir nur das aktuelle Menüelement mit Häkchen zu versehen. Das erfolgt in Zeile 15. Mit Hilfe der CType-Methode konvertieren wir den Sender-Parameter zu einem MenuItem-Objekt (zum näheren Verständnis verweise ich auf den Abschnitt »Ereignishandler« später in diesem Kapitel). Da Sender das aktuelle Menüelement darstellt, können wir seine Checked-Eigenschaft auf True setzen. Zum Schluss müssen Sie die Zeilen 23, 26 und 29 in Listing 5.1 so ändern, dass sie Ihren neuen Allround-Ereignishandler nutzen: 23: ... 26: ... 29: ...
AddHandler miTimes.Click, new EventHandler(AddressOf Me.FontClicked) AddHandler miArial.Click, new EventHandler(AddressOf Me.FontClicked) AddHandler miWing.Click, new EventHandler(AddressOf Me.FontClicked)
Die Ausführung des geänderten Codes produziert die gleichen Ergebnisse wie das ursprüngliche Listing 5.1. Der einzige Unterschied: Ein Ereignishandler erledigt die Arbeit von dreien. Nun sind Sie schon fast ein Profi in der Ereignis-Behandlung. Aber es gibt noch ein paar andere Puzzlestückchen, die wir noch zusammensetzen sollten. Woher stammen beispielsweise die Parameter Sender und EventArgs?
5.2
Delegaten
Ein Ereignis ist also, dies nur zur Erinnerung, eine Meldung, die ein Objekt abschickt, um zu signalisieren, dass eine Aktion stattgefunden hat. Wenn also eine Schaltfläche angeklickt wird, schickt sie eine »Ich wurde angeklickt«-Meldung an die Common Language Runtime (CLR). Jedes Objekt hat die Verantwortung, die CLR über Ereignisse zu informieren – sonst würde nie etwas passieren. Das das Ereignis auslösende Objekt – etwa die Schaltfläche in obigem Beispiel – wird als Sender (sender) bezeichnet (jetzt wissen Sie, warum Sie einen Sender-Parameter bei der
158
Delegaten
Deklaration eines Ereignishandlers verwendet haben). Das Objekt, das die Meldung über das Ereignis erhält und darauf antwortet, wird als Empfänger (receiver) bezeichnet. Da nun in .NET alles etwas flexibler gehandhabt wird, kann auch jedes Objekt ein Ereignis auslösen und jedes Objekt kann ein Ereignis empfangen. Dafür bestehen kaum Einschränkungen. Diese Flexibilität fordert allerdings ihren Preis: Der Sender weiß vorher nie genau, wer der Empfänger sein wird, noch kümmert er sich darum. Der Sender weiß lediglich, das er ein Ereignis auslöst, das durch etwas anderes verarbeitet wird. Man braucht also eine Art Vermittler, etwas, das als Mittelsmann zwischen Sender und Empfänger fungiert. Zum Glück stellt die CLR einen Delegaten (Delegierter) zur Verfügung, eine speziellen Typ für einen solchen Vermittler. Ein Delegat ist eine Klasse, die einen Verweis auf eine Methode enthalten kann. Das mag noch nicht viel Sinn ergeben. Einfach ausgedrückt, agiert ein Delegat als Zeiger auf einen EreignisAbsender. Er zeigt den Weg zum Empfänger. In einem vereinfachten Football-Spiel beginnt das Spiel, sobald der Quarterback (der Absender) den Ball besitzt. Er muss den Ball zu jemand anderem (dem Empfänger) in seiner Mannschaft weiterspielen. Wenn das Spiel beginnt, verfügt der Quarterback über eine beliebige Zahl von möglichen Empfängern. Die Entwicklung des Spiels entscheidet darüber, wer den Ball tatsächlich erhält. Der Quarterback überschaut das Spielfeld, um zu entscheiden, wem er den Ball zuspielen soll. Manchmal versperren Spieler der gegnerischen Mannschaft die Sicht auf manche Empfänger, und zuweilen kann der Quarterback nicht alle der verfügbaren Empfänger sehen. Noch kurz bevor er den Ball wirft, weiß der Quarterback nicht, wer diesen empfängt (manchmal sogar noch nach dem Wurf). In unserem vereinfachten Spiel sieht jedoch der Trainer an der Seitenlinie alles aus einem besseren Blickwinkel. Daher sieht er (besser als es der Quarterback jemals können wird), wer der Empfänger sein soll. Durch einen Funkempfänger hinten am Helm des Quarterbacks ruft der Trainer: »Wirf den Ball Nummer 66 zu!« (Wir wollen die Frage mal ignorieren, ob dies legal ist.) Daraufhin wirft der Quarterback den Ball zum Empfänger, den der Trainer angegeben hat. Abbildung 5.2 illustriert dieses Konzept. In diesem Beispiel stellt der Trainer den Delegaten dar, den Vermittler zwischen Absender und Empfänger, wobei er das Ereignis (den Ball) in die richtige Richtung lenkt. In .NET kann ein Objekt (der Absender) ein Ereignis »werfen«. Der Ereignisempfänger kann in jedem anderen Mitglied der Absendermannschaft bestehen beziehungsweise der Klasse, der das Objekt angehört. Ein Delegat leitet die Ereignismeldung zum richtigen Empfänger. Wenn Sie in den Begriffen von Football denken, ergibt das durchaus einen Sinn!
159
Ereignisse in Windows Forms
Potenzielle Empfänger (Mannschaftsmitglieder des Quarterback)
22
66
8
17 Verteidigung
“Nummer 66”
13 Quarterback Trainer
Abbildung 5.2: Ein Football-Quarterback – der hier als Absender fungiert – hat eine beliebige Anzahl von Empfängern (für seinen Ball).
Wie sieht also ein Delegat in Codeform aus? Sie sind vielleicht überrascht zu erfahren, dass Sie bereits einige verwendet haben. Sehen Sie sich folgendes Codefragment an: 'VB .NET Addhandler Button.Click, new EventHandler(AddressOf method) //C# Button.Click += new EventHandler(this.method);
Sie erinnern sich, dass die Definition eines Delegaten in einer Klasse besteht, die eine Referenz auf eine Methode enthält. Die Implementierung einer Klasse ist ein Objekt. Die Delegaten in diesem Codestück sind die EventHandler-Objekte. Die Objekte ScrollEventHandler, ToolBarClickEventHandler und KeyPressEventHandler, die Sie bereits in vorhergehenden Lektionen benutzt haben, sind ebenfalls Delegaten. Tatsächlich ist jede Klasse in .NET, die das Wort EventHandler im Namen enthält, ein Delegat. Das Codestück dient also im Endeffekt dazu, dem Click-Ereignis des Button-Steuerelements Delegaten hinzuzufügen. Sie zeigen den Weg zum Empfänger des Ereignisses, in diesem Fall dem method-Parameter, welcher nur eine Methode ist.
160
Delegaten
Zu den unterschiedlichen Implementierungen in VB .NET und C#: In C# besteht die einzige Möglichkeit, einem Ereignis einen Delegaten hinzuzufügen, in dem Operator +=. (Für jene unter Ihnen, die mit C++ und C nicht vertraut sind, sei gesagt, dass += ein Kurzbefehl für das Summieren und Zuweisen ist.) Zum Beispiel ist myInteger += 1;
das Gleiche wie myInteger = myInteger + 1;
Um einen Delegaten von einem Ereignis zu lösen, verwenden Sie den Operator -= (oder in Visual Basic .NET die RemoveHandler-Methode).
In VB .NET verfügen Sie andererseits über eine Reihe weiterer Optionen. Die erste Wahlmöglichkeit – die Sie bereits eine Weile eingesetzt haben – besteht im Gebrauch der AddHandler-Methode. Die Syntax bietet kaum Schwierigkeiten. Diese Methode fügt den Delegaten (der auf method zeigt) dem Click-Ereignis des Button-Objekts hinzu. Der Operator AddressOf ist lediglich ein Schlüsselwort, das das Programmäquivalent eines Zeigers auf die bereitgestellte Methode erzeugt. Als zweite und weniger gebräuchliche Option gestattet VB .NET, das Schlüsselwort Handles zu verwenden. Wie in Listing 5.3 zu sehen, fügen Sie einer Methodendeklaration das Handles-Schlüsselwort hinzu, gefolgt vom Ereignis, das zu verarbeiten ist. Listing 5.3: Eine weitere Möglichkeit, Ereignisse zu verknüpfen 1:
public sub MyEventHandler(Sender as Object, e as EventArgs) Handles
btTemp.Click 2: 3:
'tu etwas end sub
Das funktioniert genauso gut wie die anderen Methoden. Ich empfehle
Bitte beachten Sie
Benutzen Sie in VB .NET möglichst immer die
Benutzen Sie die Handles-Methode bitte sparsam. Sie schränkt Ihre Möglichkeiten dadurch ein, dass man damit nur ein Ereignis pro Handler verarbeiten kann, und sie macht Code weniger gut lesbar.
AddHandler-Methode. Sie ist nicht nur die ver-
ständlichste Methode, sondern erlaubt Ihnen auch, denselben Handler für viele verschiedene Ereignisse zu verwenden. Benennen Sie Ihre Ereignishandler gemäß einer standardmäßigen Namenskonvention. Dies macht Ihren Code später viel einfacher zu lesen und zu verstehen.
161
Ereignisse in Windows Forms
5.3
Ereignisse und Objekte
Wir wollen uns nun der Implementierung von Delegaten zuwenden. Alle vordefinierten Delegaten in .NET besitzen zwei Parameter: ein Object, welches das Objekt darstellt, das das Ereignis erzeugte, und einen zweiten Parameter, der detaillierte Angaben über das erfolgte Ereignis bereitstellt. Der gebräuchlichste Delegat ist natürlich der altbekannte EventHandler. Sobald ein Ereignis auftritt, erzeugt dieser Delegat zwei Objekte (ein Object- und ein EventArgs-Objekt) und übergibt sie dem Ereignishandler oder Empfänger, der damit machen kann, was er will. Die folgenden Abschnitte befassen sich mit der Erstellung von Delegaten und benutzerdefinierten Ereignissen.
Ereignishandler Listing 5.4 zeigt einen typischen Ereignishandler in VB .NET. Er nimmt an, dass Sie bereits ein Button-Steuerelement erzeugt und ihm einen Delegaten hinzugefügt haben, der auf die HandleClick-Methode zeigt. Beachten Sie, dass das Ereignis Button.Click den standardmäßigen EventHandler-Delegaten verwendet. Listing 5.4: Ein typischer Ereignishandler 1: 2: 3:
private sub HandleClick(Sender as Object, e as EventArgs) Microsoft.VisualBasic.MsgBox(CType(Sender, Button).Text) end sub
Denken Sie daran, dass jeder Delegat zwei Objekte erzeugt und sie an den Ereignishandler weiterleitet. Dieser wiederum, egal ob er sie verwendet oder nicht, muss diese zwei Objekte als Parameter übernehmen, wie Zeile 1 zeigt. Das erste Objekt, das wir in Zeile 1 Sender genannt haben, stellt das Objekt dar, das das Ereignis erzeugt hat. In diesem Fall wäre das Objekt das zuvor erstellte Button-Objekt. Da der Delegat, der diese Methode aufruft, vom Typ EventHandler ist, ist der zweite Parameter vom Typ EventArgs. Dieser wird dazu verwendet, weitere Informationen über das Ereignis aufzunehmen, doch im Fall von EventArgs enthält er nichts; er wird nur als Platzhalter eingesetzt. Die Entwickler bei Microsoft, die für .NET und seine Klassenbibliotheken verantwortlich zeichnen, haben dankenswerterweise standardisierte Namenskonventionen verwendet. Egal wie der Name Ihres Delegaten lautet, der zweite Parameter folgt dem gleichen Namensschema. Beispiele dafür sind EventHandler und EventArgs, ScrollEventHandler und ScrollEventArgs etc.)
162
Ereignisse und Objekte
Sie könnten sich nun Folgendes fragen: Wenn der erste Parameter das das Ereignis erzeugende Objekt darstellt, warum erstellt dann nicht statt dessen der Delegat diesen Objekttyp? Zum Beispiel ein Button-Objekt in Listing 5.4 statt eines Object-Objekts? Der Grund liegt in der Flexibilität. Wenn der Delegat einen tatsächlichen Button-Typ weitergäbe, dann bräuchte man eine Menge Typen von Delegaten: einen für jeden Objekttyp, der ein Ereignis erzeugen könnte. Der Standard-EventHandler-Delegat wäre nicht ausreichend. Daher verkleidet der Delegat das Button-Objekt als ein Object. Eine weitere Analogie: Stellen Sie sich vor, Sie erhalten per Post ein Buch. Es dürfte sich in einer Schachtel befinden. Diese Schachtel stellt das Object dar. Das Transportunternehmen steckt das Buch in eine Standardschachtel, die sich für alles eignet. Das spart dem Unternehmen die Kosten für die Erzeugung einer jeweils anderen Schachtel für unterschiedliche Warentypen, die es transportiert. Sie zumindest wissen, dass sich in der Schachtel ein Buch befindet, auch wenn Sie mit der Schachtel wenig anfangen können, und lesen wollen Sie sie auch nicht. Sie müssen daher die Schachtel öffnen und das Buch herausholen. Grob gesagt, Sie transformieren die Schachtel in ein Buch. Etwas Ähnliches passiert in der Methode in Listing 5.4. Der erste Parameter wird als Object übergeben, welches über nur wenige nützliche Eigenschaften verfügt (siehe Tag 3). Wollen Sie also mit diesem ersten Parameter arbeiten, müssen Sie ihn daher erst in den notwendigen Typ umwandeln (mit anderen Worten: die Schachtel öffnen). In Zeile 2 des Listings 5.4 verwenden Sie die CType-Methode, um den ersten Parameter von einem Object in einen Button-Typ umzuwandeln, der dann über die gewünschte Text-Eigenschaft verfügt. In C# verwenden Sie den Casting-Operator. Das ist schlicht ein Klammernpaar, das den Zieltyp einklammert ((Button) Sender).Text;
Sie sollten das Absender-Objekt stets ganz explizit in den gewünschten Typ umwandeln. VB .NET kann (je nach der Komplexität des das Ereignis erzeugenden Objekttyps) diese Umwandlung automatisch ausführen, aber es wäre nicht besonders klug, sich darauf zu verlassen, dass VB .NET Ihre Arbeit verrichtet.
Benutzerdefinierte Ereignisse erstellen Mit unserem Wissen über das Wesen von Klassen, wie man sie einsetzt und wie man Ereignisse verarbeitet, wollen wir nun einen Schritt weitergehen. Im folgenden Abschnitt erzeugen wir eine eigene Klasse, die über ihre eigenen Ereignisse verfügt, für welche Sie – oder jeder andere Entwickler – Ereignishandler bereitstellen können. Da Sie noch nicht wissen, wie man seine eigenen UI-Steuerelemente erstellt, erscheint dieses Beispiel zwar etwas an den Haaren herbeigezogen, aber es ist dessen ungeachtet eine gute Lernübung (mehr über die Erstellung eigener Steuerelemente erfahren Sie an Tag 18).
163
Ereignisse in Windows Forms
In diesem Beispiel benötigen wir zweierlei: eine benutzerdefinierte Klasse, die das benutzerdefinierte Ereignis auslöst, und eine andere Klasse, die den Handler für das benutzerdefinierte Ereignis enthält (das kann eine gewöhnliche Windows Forms-Klasse sein). Wir erzeugen eine einfache Golfball-Klasse als Klasse, die unser benutzerdefiniertes Ereignis auslöst. Der Code dafür ist in Listing 5.5 zu finden. Listing 5.5: Die GlfBall-Klasse 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19:
using System; using System.Windows.Forms; namespace TYWinforms.Day5 { public class GolfBall { public event EventHandler Sunk; protected virtual void OnSunk(EventArgs e) { if (Sunk != null) { Sunk(this,e); } } public void Putt(int intStrength) { if (intStrength = 5) { OnSunk(new EventArgs()); } } }
In diesem Code gibt es eine Menge Neues zu entdecken. In Zeile 5 deklarieren Sie Ihre benutzerdefinierte GolfBall-Klasse. Mit dieser Klasse soll ein wirklicher Golfball nachgebildet werden. Der Einfachheit halber erzeugen wir nur eine Methode dafür, Putt, die das Einputten oder Einlochen eines Golfballs simuliert. Die Methode ist in den Zeilen 14 bis 18 zu finden (gleich mehr dazu). Logischerweise wird das einzige Ereignis nur dann ausgelöst, wenn der Golfball schließlich im Loch landet. Zeile 6 deklariert das benutzerdefinierte Ereignis für diese Klasse: Sunk (eingelocht). Beachten Sie, wie einfach diese Codezeile ist; sie besteht aus lediglich vier Wörtern. Das Ereignis wird genau wie eine Klasse deklariert, doch statt das class-Schlüsselwort zu benutzen, verwenden Sie das event-Schlüsselwort. Sie brauchen ihr keine weitere Funktionalität zu verleihen, schließlich sollen nicht Sie das Ereignis behandeln, sondern der Programmierer der Klasse. Sie müssen noch den Typ des Delegaten festlegen, der das Ereignis behandeln soll. In diesem Fall verwenden wir den Standard-EventHandler-Delegaten, obwohl wir auch unseren eigenen hätten verwenden können (mehr dazu später).
164
Ereignisse und Objekte
Die Zeilen 8 bis 12 deklarieren eine ungewöhnliche Art von Methode. Sie trägt den gleichen Namen wie unser benutzerdefiniertes Ereignis, nur dass ihr das Präfix On vorangestellt ist (z.B. OnSunk). Jedes benutzerdefinierte Ereignis muss eine entsprechende Methode OnEreignisName besitzen. Diese Methode dient nur dem einen Zweck, das Ereignis auszulösen, wie man aus Zeile 10 ersieht; keine andere Methode kann das Ereignis direkt aufrufen, sondern muss dafür über diese On-Methode gehen. Die On-Methode ist besonders interessant. Zunächst ist festzuhalten, dass sie einen Parameter des Typs EventArgs übernimmt – d.h. des Typs, der dem in Zeile 6 verwendeten Typ von EventHandler entspricht. (Sie werden gleich sehen, woher dieser Parameter stammt.) Als Nächstes ist festzuhalten, dass sie in Zeile 9 den Wert von Sunk, unserem Ereignis, auszuwerten scheint. Was bedeutet das konkret? In Zeile 6 haben Sie ein neues Ereignis deklariert, dafür aber weder eine Implementierung bereitgestellt noch eine Instanz davon erzeugt. Erinnern Sie sich daran, dass Sie von den meisten Objekten zuerst eine Instanz erzeugen müssen, bevor Sie das jeweilige Objekt benutzen können. Ein Ereignis funktioniert genauso. Indem Sie hier keine Instanz erzeugten, überlassen Sie dies dem Benutzer der GolfBall-Klasse. Ereignet sich etwas, erzeugt der Benutzer Ihrer GolfBall-Klasse eine Instanz, indem er dem Ereignis einen Delegaten zuweist. Etwa so: GolfBall myGolfBall = new GolfBall(); myGolfBall.Sunk += new EventHandler(this.eineMethode)
Bis das Ereignis einen Delegaten zugewiesen bekommt, ist es nicht instantiiert und trägt daher den Wert null (oder nothing in VB .NET). Der Zweck der On-Methode besteht darin zu prüfen, ob ein Delegat zugewiesen wurde. Dies erfolgt in Zeile 9. Falls Sunk, das Ereignis, nicht gleich null ist, dann bedeutet das, dass ein Delegat zugewiesen wurde und das Ereignis ausgelöst werden kann. Würden Sie diese Prüfung nicht vornehmen und es wäre kein Delegat zugewiesen, würden Sie beim ersten Auslösen des Ereignisses eine Fehlermeldung erhalten. Das in Zeile 6 deklarierte Ereignis verwendet einen Delegaten vom Typ EventHandler. Bekanntlich sendet der EventHandler-Delegat (wie jeder andere Delegat) zwei Parameter an den Ereignishandler: ein Object, welches das das Ereignis auslösende Objekt darstellt, und einen EventArgs-Parameter. Welches Objekt löst also unser Ereignis aus? Natürlich das GolfBall-Objekt, jene Klasse, in der sich all dieser Code befindet. Daher ist der dem Ereignis zu übergebende Parameter lediglich this, das für die aktuelle Klasse steht. Merke: Der erste Parameter zur Auslösung eines Ereignisses auf diese Weise ist stets this (oder me in VB .NET). Um mehr über den zweiten Parameter zu erfahren, müssen wir zu Zeile 14 springen. Diese Methode, Putt, führt die OnSunk-Methode unter bestimmten Bedingungen aus. Wollten Sie irgendwelche ereignisrelevanten Informationen übergeben, würden Sie sie
165
Ereignisse in Windows Forms
hier erzeugen. Da es aber für dieses Ereignis keine gibt, erzeugen wir einfach einen neuen EventArgs-Parameter und übergeben ihn an die OnSunk-Methode. Lassen Sie uns zusammenfassen. Als Erstes erzeugt der Benutzer unserer GolfBall-Klasse eine Instanz dieser Klasse und ein Delegat wird dem Sunk-Ereignis zugewiesen. Der Benutzer ruft die Putt-Methode auf, um zu versuchen, den »Golfball einzulochen«. Sobald festgestellt wurde, dass dem Putter genügend Kraft verliehen wurde (wie durch den InStrength-Parameter angegeben wird), wird die OnSunk-Methode ausgeführt, wobei ihr ein EventArgs-Parameter übergeben wird. Die OnSunk-Methode prüft wiederum, ob ein Delegat zugewiesen wurde. Für diesen Fall löst sie das Sunk-Ereignis aus und übergibt ihm eine Repräsentation des aktuellen Objekts und des EventArgs-Parameters. Wenn das Sunk-Ereignis ausgelöst wird, veranlasst der Delegat, dass der vom Benutzer zugeordnete Ereignishandler ausgeführt wird. Wir wollen uns als Nächstes einmal die Code-Seite des Benutzers der GolfBall-Klasse ansehen (im Gegensatz zu der des Endanwenders). Sie ist in Listing 5.6 zu sehen. Listing 5.6: Die Benutzerseite des GolfBall-Beispiels 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26:
166
public class MainForm : Form { private Button btPutt = new Button(); private Label lblStrength = new Label(); private TextBox tbStrength = new TextBox(); private GolfBall gfBall = new GolfBall(); public MainForm(){ lblStrength.Text = "Spielstärke:"; lblStrength.Location = new Point(50,50); tbStrength.Text = 0.ToString(); tbStrength.Location = new Point(125,50); btPutt.Text = "Lochen Sie den Ball ein!"; btPutt.Location = new Point(100,100); btPutt.Click += new EventHandler(this.PuttBall); gfBall.Sunk += new EventHandler(this.BallSunk); this.Text = "Beispiel für ein angepasstes Ereignis"; this.Controls.Add(btPutt); this.Controls.Add(tbStrength); this.Controls.Add(lblStrength); } public void PuttBall(Object Sender, EventArgs e) {
Ereignisse und Objekte
27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38:
gfBall.Putt(Convert.ToInt32(tbStrength.Text)); } public void BallSunk(Object Sender, EventArgs e) { MessageBox.Show("Eingelocht!"); } public static void Main() { Application.Run(new MainForm()); } } }
Speichern Sie dieses und Listing 5.5 in einer einzigen Datei. In Listing 5.6 findet sich nichts Besonderes. In den Zeilen 2 bis 5 erzeugen Sie ein paar Steuerelemente, darunter auch eine Instanz der GolfBall-Klasse. Die Zeilen 8 bis 16 platzieren die Steuerelemente im Formular und stellen ein paar Eigenschaften ein. Die Steuerelemente erlauben es dem Benutzer, einen Spielstärke-Wert einzugeben und eine Schaltfläche anzuklicken, um den Ball »einzulochen«. In Zeile 18 fügen Sie der Sunk-Methode der GolfBall-Klasse einen Delegate hinzu, genau wie Sie das bei jeder anderen Klasse täten. Die Zeilen 20 bis 23 fügen Ihrem Formular alle Steuerelemente hinzu. Die PuttBall-Methode in Zeile 26 wird immer dann ausgeführt, wenn die in Zeile 2 deklarierte Schaltfläche angeklickt wird. Sie führt schlicht die SunkMethode der GolfBall-Klasse aus, welche bereits in Listing 5.5 in Zeile 14 deklariert worden war. Sie übergibt den Spielstärke-Wert, den der Benutzer in das Textfeld eingegeben hat. Jedes Mal wenn das Sunk-Ereignis ausgelöst wird, gelangt die BallSunk-Methode zur Ausführung. In unserem Fall wird Sunk aufgerufen, sobald die Putt-Methode mit einem Spielstärke-Wert von genau 5 aufgerufen wird. BallSunk (der Ball wurde eingelocht) ruft die Show-Methode der MessageBox-Klasse auf (die sich von der MsgBox-Funktion des Namensraums Microsoft.VisualBasic unterscheidet), um eine einfache Meldung anzuzeigen, die angibt, dass der Ball im Loch versenkt (daher »sunk«) wurde. Kompilieren Sie diese Anwendung und führen Sie sie aus. Tragen Sie verschiedene Werte in das Textfeld ein und beachten Sie, dass nichts passiert, wenn Sie auf die Schaltfläche klicken, es sei denn, Sie hätten einen Spielstärke-Wert 5 eingegeben. Das Ergebnis ist in Abbildung 5.3 zu sehen.
167
Ereignisse in Windows Forms
Abbildung 5.3: Unser benutzerdefiniertes Sunk-Ereignis in voller Aktion – der Benutzer gewinnt!
Steuerelemente sezieren Aufgrund des Wissens, über das Sie nun verfügen, können Sie eine Hypothese darüber aufstellen, wie Objekte, die Sie kennen, implementiert werden (was Ihnen helfen wird, sobald Sie Ihre eigenen Steuerelemente erstellen). Nehmen Sie das Button-Steuerelement als Beispiel. Obwohl Sie nicht wissen, wie es genau erstellt wurde, können Sie aufgrund dessen, was Sie heute gelernt haben, dennoch darauf schließen. Lassen Sie uns eine Eigenschaft, nämlich Text, und ein Ereignis, Click, genauer untersuchen (den Darstellungsvorgang wollen wir vorerst ignorieren; wir besprechen ihn an Tag 13). Wir wollen uns mit der Frage befassen, wie wir das heute Gelernte auf nützliche Objekte anwenden können. Als Erstes können Sie davon ausgehen, dass die Text-Eigenschaft eine einfache Variable ist, die wie folgt im Quellcode des Buttons deklariert wird: public string Text;
Als Nächstes muss die Button-Klasse das Click-Ereignis deklarieren, so wie im folgenden Codestück: public event EventHandler Click;
Da alle Ereignisse eine korrespondierende On-Methode besitzen müssen, deklarieren wir diese im nächsten Schritt: protected virtual void OnClick(EventArgs e) { if (Click != null) { Click(this, e); } }
Das war schon alles! In Listing 5.7 kommt alles zusammen.
168
Ereignisse und Objekte
Listing 5.7: Die extrapolierte Button-Klasse 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19:
using System; using System.Windows.Forms; namespace System.Windows.Forms { public class Button : ButtonBase { public string Text; public event EventHandler Click; protected virtual void OnClick(EventArgs e){ if (Click != null){ Click(this,e); } } public Button(){ // Code zur Initialisierung und Anzeige der Schaltfläche } } }
Dieser Code ist natürlich vereinfacht. Wir wissen ja, dass das Button-Steuerelement über mehr als nur eine Eigenschaft und ein Ereignis verfügt. Tatsächlich wurde das Click-Ereignis von der Control-Klasse geerbt, daher müssten wir es eigentlich nicht hier deklarieren, aber das vermittelt Ihnen eine gute Vorstellung davon, was das Erstellen von Steuerelementen bedeutet.
Benutzerdefinierte Delegaten erstellen Wenn Sie Ihre eigenen benutzerdefinierten Ereignisse erzeugen, müssen Sie häufig auch Ihre eigenen benutzerdefinierten Delegaten und EventArgs-Objekte erzeugen. Den Delegaten zu erstellen, ist einfach; das EventArgs-Objekt ist etwas komplizierter, doch Sie sind bereits in der Lage, das anzupacken. Die Deklaration des Delegaten besteht aus einer Zeile mit dem delegate-Schlüsselwort: public delegate void MyEventHandler(Object Sender, MyEventArgs e);
Diese Zeile erzeugt einen neuen Delegaten namens MyEventHandler, der sich für jedes Ihrer benutzerdefinierten Ereignis verwenden lässt. Er stellt einen EventArgs-Parameter bereit, ein benutzerdefiniertes Objekt, das wir ebenfalls zu erstellen haben (beide Objekte befolgen die Standardnamenskonventionen). Genau wie bei einem Ereignis müssen Sie weder eine Implementierung noch Code für diesen Delegaten bereitstellen; die CLR erledigt das alles für Sie.
169
Ereignisse in Windows Forms
Zum Erstellen des zweiten Parameters gehört das Erzeugen Ihrer eigenen benutzerdefinierten Klasse mit den Eigenschaften, die Ihr Ereignis benötigt, wie in Listing 5.8 demonstriert wird: Listing 5.8: Eine benutzerdefinierte EventArgs-Klasse erstellen 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14:
using System; using System.Windows.Forms; namespace TYWinforms.Day5 { public class GolfBallEventArgs : EventArgs { public readonly int Meter; public readonly bool Save; public GolfBallEventArgs(int Meter,bool Save){ this.Meter = Meter; this.Save = Save; } } }
Diese Klasse namens GolfBallEventArgs werden wir nutzen, um einem Ereignishandler benutzerdefinierte Ereignisinformationen zukommen zu lassen. Sie erbt von der Klasse EventArgs, so dass wir keine Funktionen der Letzteren duplizieren müssen. In den Zeilen 6 und 7 deklarieren Sie zwei Eigenschaften: Meter, die angibt, aus welcher Entfernung der Golfball beim Einputten eingelocht wurde; und Save, die angibt, ob der Einputt-Schlag für Par gespeichert wurde. Diese Werte sind auf readonly (Nur-Lesen) gesetzt, so dass sie sich durch nichts anderes als durch den Konstruktor selbst ändern lassen. Dieses Vorgehen ist Standard, denn schließlich gilt: Nachdem diese Werte gesetzt sind, gibt es keinen Grund mehr, sie zu ändern. In VB .NET würden die entsprechenden Zeilen wie folgt aussehen: ReadOnly Meter as Integer ReadOnly Save as Boolean
In Zeile 9 stoßen wir auf den Konstruktor unserer GolfBallEventArgs-Klasse. Sie übernimmt zwei Parameter, nämlich Meter und Save, die mit den readonly-Eigenschaften, die gerade erstellt wurden, korrespondieren. Dieser Konstruktor dient lediglich dazu, die Parameterwerte den entsprechenden Eigenschaften der Klasse zuzuweisen. Wir wollen einmal unsere GolfBall-Klasse so ändern, dass sie diesen neuen Delegaten und das EventArgs-Objekt verwendet. Der neue Code, der auf dem modifizierten Listing 5.5 basiert, ist in Listing 5.9 zu sehen.
170
Ereignisse und Objekte
Listing 5.9: Den benutzerdefinierten Delegaten verwenden 1: using System; 2: using System.Windows.Forms; 3: using System.Drawing; 4: 5: namespace TYWinforms.Day5 { 6: public class GolfBallEventArgs : EventArgs { 7: public readonly int Meter; 8: public readonly bool Save; 9: 10: public GolfBallEventArgs(int Meter,bool Save){ 11: this.Meter = Meter; 12: this.Save = Save; 13: } 14: } 15: 16: public delegate void GolfBallEventHandler(Object Sender, GolfBallEventArgs e); 17: 18: public class GolfBall { 19: public event GolfBallEventHandler Sunk; 20: 21: protected virtual void OnSunk(GolfBallEventArgs e){ 22: if (Sunk != null){ 23: Sunk(this,e); 24: } 25: } 26: 27: public void Putt(int intStrength){ 28: if (intStrength == 5){ 29: Random rndGen = new Random(); 30: OnSunk(new GolfBallEventArgs(rndGen.Next(1,51), Convert.ToBoolean(rndGen.Next(0,2)))); 31: } 32: } 33: }
Die Zeilen 5 bis 14 umfassen die GolfBallEventArgs-Klasse, die wir gerade entwickelt haben (Listing 5.8). Zeile 16 enthält die Deklaration des Delegaten, den diese Klasse verwendet. Innerhalb der GolfBall-Klasse haben nur wenige Änderungen stattgefunden. In Zeile 19 deklarieren Sie das Sunk-Ereignis mit Hilfe des GolfBallEventHandler-Delegaten anstelle von EventHandler. Auf ähnliche Weise verwenden Sie in den Zeilen 21 und 30 GolfBallEventArgs statt EventArgs. Diesmal verhält sich die Putt-Methode jedoch ein wenig anders als gewohnt.
171
Ereignisse in Windows Forms
Weil sie das GolfBallEventArgs-Objekt jetzt mit zwei Werten versorgen muss, nämlich Meter und Save, müssen wir ein paar Überstunden einlegen. Es geht uns nicht darum, wie die Werte aussehen, Hauptsache, sie werden zurückgegeben. Daher übergeben wir einfach ein paar zufällige Zahlen. In Zeile 29 erzeugen wir eine Instanz der Random-Klasse, die alle Arten von Methoden enthält, die Zufallszahlen generieren, darunter auch die von uns verwendete Methode, Next. Sie übernimmt zwei Parameter, beide Integer, die einen Wertebereich angeben, aus dem eine Zufallszahl zurückgegeben wird (Next liefert einen GanzzahlInteger, ohne Dezimalstellen). Für Meter, den ersten Parameter, wählen wir irgendeine Zahl zwischen 1 und 50 aus (im Bereich darüber dürfte es kaum jemanden geben, der aus dieser Distanz einputten kann!). Für den zweiten Parameter benötigen wir einen booleschen Wert. Dieser kann True, False, 1 oder 0 sein. Daher benutzen wir Next, um eine Zufallszahl im Bereich zwischen 0 und 1 zurückzugeben (und da Next nur Ganzzahlen zurückgibt, muss das entweder 0 oder 1 sein, aber nichts dazwischen). Mit Hilfe der Convert.ToBoolean-Methode wandeln wir die zurückgegebene Zufallszahl in einen booleschen Wert um, um sie dem GolfBallEventArgsKonstruktor zu übergeben. Wir können den Ereignishandler für das Sunk-Ereignis so ändern, dass er dieses Objekt verwendet. Ändern Sie mit Hilfe der gleichen MainForm-Klasse aus Listing 5.6 die Zeile 31 wie folgt: MessageBox.Show("Eingelocht aus " + e.Meter.ToString() + " Metern Entfernung!");
Abbildung 5.4 zeigt das Ergebnis dieser letzten Änderung.
Abbildung 5.4: Unser benutzerdefiniertes Sunk-Ereignis mit dem Objekt GolfBallEventArgs gibt dem Benutzer Anlass zum Jubeln!
172
Zusammenfassung
5.4
Zusammenfassung
Ereignisse sind ein zentraler Bestandteil einer Windows Forms-Anwendung. Ohne sie würde man nichts damit erledigen können. Wie das Ereignissystem funktioniert, wissen Sie nun sowohl aus der Perspektive des Entwicklers als auch aus der des Benutzers. Ein Ereignishandler ist diejenige Methode, die ausgeführt wird, sobald ein Ereignis stattfindet. Zur Erstellung des Handlers gehört das Verknüpfen von Ereignis und Handler mit Hilfe der AddHandler-Methode in VB .NET, oder mit dem +=-Operator in C#. Ereignishandler weisen eine Standardsignatur auf, die nur wenige Variationen zulässt. Ein Delegat ist eine Klasse, die eine Referenz auf einen Ereignishandler enthält. Er stellt die »Drähte« zwischen einem Ereignis und seinen Handlern dar. In .NET befolgen alle Delegaten das Namensschema NameEventHandler. Delegaten übergeben den Ereignishandlern zwei Parameter: ein Object-Objekt, das das ereignisauslösende Objekt darstellt, und einen EventArgs-Parameter, der zusätzliche Informationen bereitstellt. Der EventArgsParameter befolgt das gleiche Namensschema wie der Delegat. Um Ihr eigenes (benutzerdefiniertes) Ereignis zu erstellen, müssen Sie es lediglich mit Hilfe des event-Schlüsselwortes deklarieren und einen passenden Delegaten dafür wählen. Jedes Ereignis muss eine entsprechende OnEreignisName-Methode besitzen, die zur Auslösung des Ereignisses verwendet wird. Diese Methode kann ebenso prüfen, ob ein Delegat »verdrahtet« wurde, indem sie überprüft, ob das Ereignis einen Wert null (oder nothing in VB .NET) ergibt. Mit Hilfe des delegate-Schlüsselworts können Sie Ihre eigenen Delegaten auf die gleiche Weise erstellen wie ein benutzerdefiniertes Ereignis. Ein Delegat benötigt weder Implementierung noch Code – die CLR erzeugt beides für Sie. Ihre benutzerdefinierten Delegaten können jeweils einen vordefinierten EventArgs-Parameter verwenden, oder Sie können Ihren eigenen erstellen, indem Sie eine Klasse erzeugen, die vom EventArgs-Objekt erbt. Ihr benutzerdefiniertes EventArgs-Objekt muss nur die benutzerdefinierten readonlyEigenschaften bereitstellen, die Sie wollen, sowie einen Konstruktor, um diesen Eigenschaften Werte zuweisen zu können.
5.5 F
Fragen und Antworten
Woher weiß man, welche Art von Delegat man für ein Ereignis benutzen muss? A
Leider gibt es keine leichte Methode das herauszufinden. In den meisten Fällen verwendet man den Standard-EventHandler-Delegaten, so dass man damit auf der sicheren Seite ist. Ist dies aber nicht der korrekte Delegat, dann wird Ihnen normalerweise Ihr Compiler (Visual Studio .NET oder die Befehlszeilencompiler
173
Ereignisse in Windows Forms
csc.exe und vbc.exe) mitteilen, welchen Sie verwenden sollten. In diesem Buch weisen wir normalerweise auf den passenden Delegaten darauf hin.
F
Kann ich einem einzelnen Ereignis mehr als einen Delegaten zuweisen? A
Durchaus. Sie dürfen dies tun, wenn Sie wünschen, dass ein Einzelereignis mehr als eine Aktion ausführt. In unserem Vergleich mit dem Auto entspräche dies einer Ihrer Aktionen (sagen wir, Sie treten auf das Gaspedal), aber mehreren Aktionen des Wagens (bewegt sich, die Tachonadel zittert, Ausschalten des Tempomaten usw.). Das ist aber keine gebräuchliche Praxis, da normalerweise ein Ereignishandler alle gewünschten Aktionen ausführen kann. Es ist jedoch eine schlaue Möglichkeit, Ereignishandler zu »erweitern«. Mal angenommen, Sie hätten mit ShowText und ShowTextAndAlert zwei Ereignisse. Sie müssen Text in einem Bezeichnungsfeld anzeigen, doch das zweite Ereignis muss außerdem ein Meldungsfenster mit einer Warnung anzeigen. Es besteht kein Grund dafür, den Code, der den Text zur Anzeige bringt, zu duplizieren. Daher kann man beiden Ereignissen einen einzigen Delegaten zuweisen. Das ShowTextAndAlert-Ereignis würde einen weiteren Delegaten zugewiesen bekommen, der aber das Anzeigen des Meldungsfeldes veranlasst. Sobald das ShowTextAndAlert-Ereignis ausgelöst wird, würden beide Delegaten ausgeführt werden, so dass Text angezeigt und ein Meldungsfeld geöffnet wird. Die Ereignishandler werden in der Reihenfolge ausgeführt, die ihnen die Delegaten zuweisen.
5.6
Workshop
Dieser Workshop soll Ihnen helfen, sich besser an die heute behandelten Begriffe zu erinnern. Es hilft Ihnen sehr, die Antworten vollständig zu verstehen, bevor Sie weiterlesen. Die Antworten zu den Quizfragen und den Übungen finden Sie in Anhang A.
Quiz 1. Wie sieht die standardmäßige Signatur eines Ereignishandlers aus? 2. Erzeugen Sie in C# ein Ereignis namens ShowText, das den Delegaten KeyPressEventHandler verwendet. 3. Erstellen Sie einen benutzerdefinierten Delegaten, der das Objekt KeyPressEventArgs verwendet.
174
Workshop
4. Wahr oder falsch? Um Objekttypen miteinander zu vergleichen, verwendet man den Gleichheitszeichenoperator (=). 5. Welche der folgenden Aussagen ist inkorrekt? AddHandler btOne.Click, new EventHandler(AddressOf btOne_Click) public sub MyEventHandler(Sender as Object, e as EventArgs) Handles btTemp.Click Addhandler btOne.Click += new EventHandler(AddressOf btOne_Click)
6. Warum sollten die Eigenschaften eines benutzerdefinierten EventArgs-Objekts readonly sein? 7. Ist das folgende Codestück korrekt? Was sollte man falls nötig ändern? public virtual void OnMyEvent(EventArgs e) { MyEvent(this, e); }
8. An welcher Stelle (im Quellcode) muss man benutzerdefinierte Ereignisse und Delegaten deklarieren?
Übung Erstellen Sie in C# eine Taschenrechner-Anwendung wie den Windows-Taschenrechner. Er sollte Zifferntasten aufweisen, mit denen sich Zahlen eingeben lassen, sowie Funktionstasten, die Berechnungen vornehmen, wenn man darauf klickt. Verwenden Sie einen Ereignishandler für alle Zifferntasten und einen Handler für alle Funktionstasten. (Tipp: Nehmen Sie verborgene Steuerelemente, also solche, deren Visible-Eigenschaft auf False gesetzt ist – damit können Sie temporäre Variablen speichern.)
175
Windows Forms mit Steuerelementen erweitern
6
Windows Forms mit Steuerelementen erweitern
In der heutigen Lektion runden Sie Ihr wachsendes Repertoire an Windows Forms-Steuerelementen ab. Bis jetzt haben Sie nur Menüs, Bildlaufleisten, Statusleisten, Symbolleisten, Textbox, Bezeichnungsfelder und Schaltflächen als Steuerelemente zu sehen bekommen. Heute erfahren Sie etwas über Kombinationsfelder und Optionsfelder, die dem Benutzer mehrere Auswahlmöglichkeiten bereitstellen, sodann Timer, die sich zur Zeitmessung von Ereignissen verwenden lassen, und schließlich Strukturansichten (Treeviews), mit denen der Benutzer das Dateisystem erkunden kann, und viele andere Steuerelemente. Sie lernen darüber hinaus, wie man die Windows Forms-Merkmale für automatisches Layout nutzt, um eine übersichtlichere und leichter zu pflegende Benutzeroberfläche zu präsentieren. Heute lernen Sie, wie Sie 쐽
vom Benutzer Informationen einholen und sie auf logische Art und Weise anzeigen,
쐽
den Benutzer zwingen, Entscheidungen zu treffen,
쐽
Benutzereingaben formatieren,
쐽
Grafiken in Ihren Anwendungen anzeigen,
쐽
zeitmessende Anwendungen erstellen ,
쐽
viele verfügbare Aufzählungen nutzen, um Ihre Steuerelemente anzupassen,
쐽
die Positionierung Ihrer Steuerelemente steuern.
Wenn Sie die heutige Lektion durcharbeiten, sollten Sie an ein paar Schlüsselaspekte bezüglich der Windows Forms-Steuerelemente denken. Jedes Windows Forms-Steuerelement im .NET Framework erbt direkt oder indirekt von der Control-Klasse, welche wiederum von der Object-Klasse erbt. Das bedeutet, dass diese Steuerelemente eine ganze Reihe von Eigenschaften und Methoden gemeinsam haben, darunter die GetTypeMethode, die Height- und Width-Eigenschaften, das Click-Ereignis und andere, die Sie heute kennen lernen. (Um eine vollständige Aufzählung dieser gemeinsamen Mitglieder zu erhalten, schlagen Sie bitte in Anhang B nach.) All diese Steuerelemente haben auch Regeln für ihre Verwendung gemeinsam. Sie werden sehen, dass Sie nach dem Erlernen des Umgangs mit einem Steuerelement auch mit den anderen gut zurechtkommen werden. Natürlich verfügt jedes Steuerelement über seine Eigenarten und eigenen Mitglieder, aber sie folgen alle dem gleichen Muster.
178
Das Steuerelement Button
6.1
Das Steuerelement Button
Das Steuerelement Button kennen Sie ja bereits. Seine grundlegendste Eigenschaft ist Text. Sie setzt die Aufschrift, die auf der Schaltfläche angezeigt wird, um dem Benutzer einen Hinweis darauf zu geben, was die Schaltfläche bewirkt. TextAlign positioniert die Aufschrift auf der Schaltfläche und kann die folgenden Werte der ContentAlign-Aufzählung annehmen: BottomCenter, BottomLeft, BottomRight, MiddleCenter, MiddleLeft, MiddleRight, TopCenter, TopLeft und TopRight – ein Wert für jede Position auf der Schaltfläche. Die FlatStyle-Eigenschaft gestattet Ihnen die Anpassung des Aussehens der Schaltfläche. Die Eigenschaft kann einen der Werte in der FlatStyle-Aufzählung annehmen: Flat, Popup, wobei die Schaltfläche flach aussieht, bis Sie mit der Maus darüber geraten, Standard als Standardwert oder System, bei dem sich das Aussehen der Schaltfläche nach dem Standard Ihres Betriebssystems richtet (wobei dieser Wert einem der drei vorhergehenden Werte entsprechen muss). Abbildung 6.1 zeigt die ersten drei Stile sowie die Verwendung der TextAlign-Eigenschaften.
Abbildung 6.1: Beachten Sie, dass sich die Beschriftung der Schaltflächen in diesen Beispielen für FlatStyle-Werte jeweils an einer anderen Position befindet. Das ist eine Funktion der Eigenschaft TextAlign.
Mit Button-Steuerelementen können Sie Grafiken auf zweierlei Weise darstellen. Sie können entweder die Eigenschaften Image und ImageAlign verwenden oder die Eigenschaften ImageList und ImageIndex (wie bereits bei den Symbolleisten an Tag 4). ImageAlign verwendet die gleichen Werte der ContentAlign-Aufzählung wie TextAlign, wohingegen Image ein Image-Objekt übernimmt: button1.Image = Image.FromFile("C:\WinForms\Day6\MyBitmap.bmp")
Sie wissen bereits, wie man Click, das Hauptereignis eines Button-Steuerelements verarbeitet. Das folgende Codestück zeigt die Zuweisung eines Delegaten zur Click-Methode und einen exemplarischen Ereignishandler: AddHandler Button1.Click, new EventHandler(AddressOf Me.Button1_Click) ... private sub Button1_Click(Sender as Object, e as EventArgs) MessageBox.Show("Sie haben angeklickt: " & Ctype(Sender, Button).ToString end sub
Das Button-Steuerelement besitzt die Methode PerformClick, die Ihnen die Simulation eines Schaltflächen-Klicks gestattet. Sie können also das Click-Ereignis genauso auslösen,
179
Windows Forms mit Steuerelementen erweitern
als hätte ein Benutzer die Schaltfläche angeklickt. Dieser Methode müssen Sie keine Parameter bereitstellen; die CLR wird sie für Sie generieren: Button1.PerformClick
Das ist recht nützlich, wenn Sie einmal den Handler des Click-Ereignisses aufrufen müssen; durch PerformClick brauchen Sie sich nicht mehr um die Erstellung von EventArgsObjekten usw. zu kümmern.
6.2
Die Steuerelemente CheckBox und RadioButton
Die Steuerelemente CheckBox und RadioButton sind einander sehr ähnlich. Beide gestatten dem Benutzer, eine (oder mehrere) Auswahl(en) aus einer Gruppe ähnlicher Elemente zu treffen. Wahrscheinlich haben Sie diese Steuerelemente bereits einmal beim Ausfüllen eines Webformulars benutzt. Sie unterscheiden sich dadurch, dass es das Kombinationsfeld CheckBox dem Benutzer gestattet, mehr als ein Element der Angebotsliste auszuwählen, während das Optionsfeld RadioButton nur eine Auswahl gestattet. Listing 6.1 zeigt ein Beispiel für die Verwendung dieser Steuerelemente. Listing 6.1: Ein Beispiel für den Einsatz der Steuerelemente RadioButton und CheckBox 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22:
180
Imports System Imports System.Windows.Forms Imports System.Drawing namespace TYWinforms.Day6 public class Listing61 : Inherits Form private lblColor as new Label private lblSex as new Label private private private private private private
chkColorBlue as new CheckBox chkColorRed as new CheckBox chkColorYellow as new CheckBox chkColorGreen as new CheckBox rbSexMale as new RadioButton rbSexFemale as new RadioButton
public sub New lblColor.Text = "Wählen Sie Ihre Lieblingsfarbe(n)" lblColor.Location = new Point(10,10) lblColor.AutoSize = True
Die Steuerelemente CheckBox und RadioButton
23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65:
chkColorBlue.Text = "Blau" chkColorBlue.Location = new Point(10,25) chkColorBlue.Width = 50 chkColorRed.Text = "Rot" chkColorRed.Location = new Point(60,25) chkColorRed.Width = 50 chkColorYellow.Text = "Gelb" chkColorYellow.Location = new Point(110,25) chkColorYellow.Width = 60 chkColorGreen.Text = "Grün" chkColorGreen.Location = new Point(170,25) chkColorGreen.Width = 60 lblSex.Text = "Wählen Sie Ihr Geschlecht" lblSex.Location = new Point(10,50) lblSex.AutoSize = True rbSexMale.Text = "Männlich" rbSexMale.Location = new Point(10,75) rbSexMale.Width = 50 rbSexFemale.Text = "Weiblich" rbSexFemale.Location = new Point(60,75) rbSexFemale.Width = 60 Me.Controls.Add(lblColor) Me.Controls.Add(lblSex) Me.Controls.Add(chkColorBlue) Me.Controls.Add(chkColorRed) Me.Controls.Add(chkColorYellow) Me.Controls.Add(chkColorGreen) Me.Controls.Add(rbSexMale) Me.Controls.Add(rbSexFemale) end sub public shared sub Main() Application.Run(new Listing61) end sub end class end namespace
181
Windows Forms mit Steuerelementen erweitern
Dieses Listing ist sehr einfach aufgebaut. Alle hier verwendeten Steuerelemente werden in den Zeilen 8 bis 16 deklariert, und die Eigenschaften Text, Location und Width werden in den Zeilen 19 bis 49 festgelegt. Die Steuerelemente werden dann dem Formular in den Zeilen 51 bis 58 hinzugefügt. Listing 6.1 produziert das in Abbildung 6.2 dargestellte Ergebnis.
Abbildung 6.2: Die Steuerelemente RadioButton und CheckBox sind in Formularen sehr nützlich. CheckBox-Steuerelemente erlauben Ihnen, jede beliebige Kombination von Kästchen im Containersteuerelement auszuwählen, welches in diesem Fall das Formular ist. RadioButtons andererseits erlauben Ihnen nur eine Auswahl je übergeordnetem Steuerelement – also pro Formular in diesem Fall. Um getrennte Gruppen von RadioButtons mit nur jeweils
einem wählbaren Wert zu erstellen, gruppieren Sie die Steuerelemente durch Positionierung auf einem Panel-Steuerelement. (Ein Panel-Steuerelement gestattet lediglich die Gruppierung von Steuerelementen; fassen Sie einfach jeden Satz von RadioButton-Steuerelementen, die denselben Wert beschreiben, auf einem separaten Panel-Steuerelement zusammen.) RadioButtons, die sich auf verschiedenen übergeordneten Steuerelementen befinden, schließen sich gegenseitig aus. Die Steuerelemente CheckBox und RadioButton haben ein paar Eigenschaften gemein. Die Checked-Eigenschaft lässt sich dazu verwenden, zu prüfen oder zu bestimmen, ob das angegebene Steuerelement ausgewählt ist. Die CheckAlign-Eigenschaft ähnelt der TextAlignEigenschaft des Button-Steuerelements. Sie prüft, wie das Auswahlfeld bezüglich des Textes ausgerichtet ist. CheckAlign verwendet ebenfalls die ContentAlignment-Aufzählung. Die AutoCheck-Eigenschaft stellt fest, ob sich das Aussehen des Steuerelements automatisch ändern sollte, wenn es angeklickt wird. Sollte also ein Häkchen erscheinen, sobald die Auswahl angeklickt wurde? Setzen Sie diese Eigenschaft auf False, wenn Sie anhand der Checked-Eigenschaft das Steuerelement selbst mit einem Häkchen versehen wollen. Der typische Fall dafür träte ein, wenn eine Art von Gültigkeitsprüfung erfolgen müsste, bevor
182
Das Steuerelement ComboBox
ein Steuerelement angeklickt werden darf (etwa bei der Gültigkeitsprüfung von Benutzereingaben). Auch Appearance ist eine vielen Steuerelementen gemeinsame Eigenschaft. Mit Hilfe von Werten aus der Appearance-Aufzählung (Button oder Normal) bestimmt sie, ob das Steuerelement normal aussieht (Voreinstellung) oder als Schaltfläche erscheint, die sich ausoder einschalten lässt. Die Funktionalität ist die gleiche, doch dies bietet dem Benutzer ein leicht verändertes Erscheinungsbild der Benutzeroberfläche. Die beiden Steuerelemente haben ein Ereignis gemein: CheckChanged. Wie der Name schon andeutet, tritt es auf, sobald das Kontrollhäkchen für das Steuerelement gesetzt oder entfernt wird. Auf Grund seiner etwas komplexeren Funktionsmerkmale verfügt das CheckBox-Steuerelement über einige weitere Eigenschaften. Die CheckState-Eigenschaft ähnelt der CheckedEigenschaft, bietet aber etwas mehr Möglichkeiten. Sie muss einen der Werte aus der CheckState-Aufzählung annehmen: Checked, Unchecked oder Indeterminate. Die ersten beiden bieten die gleiche Funktion wie die Checked-Eigenschaft (s.o.), während Indeterminate einen dritten Zustand des Steuerelements beschreibt; das Steuerelement ist mit Häkchen versehen, doch grau dargestellt, so dass es den Eindruck vermittelt, nur teilweise »angekreuzt« zu sein. Fügen Sie in Listing 6.1 nach Zeile 25 folgende Zeile hinzu und kompilieren Sie die Anwendung neu, um diesen Zustand zu sehen: chkColorBlue.CheckState = CheckState.Indeterminate
Per Voreinstellung können Sie diesen »unbestimmten« Zustand nur im Code auswählen. Wenn Sie jedoch die ThreeState-Eigenschaft auf true setzen, dann kann auch der Benutzer auf diesen Zustand umschalten. Um mit der CheckState-Eigenschaft fortzufahren: Das Ereignis CheckStateChanged wird ausgelöst, sobald diese Eigenschaft sich ändert, was doch dem Vorgang bei CheckChanged recht ähnlich ist, wenn sich die Eigenschaft Checked ändert. Da sowohl CheckBox als auch RadioButton von derselben Klasse erben wie das ButtonSteuerelement (ButtonBase), teilen sie viele der gleichen Eigenschaften: Text, TextAlign, PerformClick und FlatStyle, um nur ein paar Beispiele zu nennen.
6.3
Das Steuerelement ComboBox
Das ComboBox-Steuerelement (Kombinationsfeld) ist recht interessant; es kombiniert ein editierbares Textfeld (wie eine TextBox) mit einer Dropdown-Liste, aus der der Benutzer vorgegebene Optionen wählen kann. Wegen dieser zweifachen Funktionalität verfügt das Steuerelement über eine Reihe von Eigenschaften, um sein Aussehen zu bestimmen und ihm Wahlmöglichkeiten zuzuweisen.
183
Windows Forms mit Steuerelementen erweitern
Das Kombinationsfeld instantiieren Das Erstellen dieses Steuerelements verläuft wie bei jedem anderen auch: dim cboMyList as new ComboBox
Sie können die Eigenschaften Top, Left oder Location dazu verwenden, das Steuerelement im Formular zu positionieren, sowie die Eigenschaften Font, BackColor und andere, allen Steuerelementen zugängliche Eigenschaften, um sein Aussehen anzupassen. Sie können sogar die Text-Eigenschaft nutzen, um eine Zeichenfolge in der Textbox-ähnlichen Sektion des Steuerelements anzuzeigen. Bei den interessanteren Eigenschaften geht es jedoch um das Füllen der Dropdown-Liste. Die einfachste Möglichkeit, dem Kombinationsfeld solche Elemente hinzuzufügen, besteht in der Verwendung der Items-Auflistung und der Methoden Add oder AddRange. Die erste fügt der Liste ein einzelnes Element hinzu, die zweite ein komplettes Array von Objekten. Listing 6.2 demonstriert beide Methoden. Listing 6.2: Einer Dropdown-Liste in einer ComboBox neue Elemente hinzufügen 1: Imports System 2: Imports System.Windows.Forms 3: Imports System.Drawing 4: 5: namespace TYWinforms.Day6 6: 7: public class Listing62 : Inherits Form 8: private cboMyList as new ComboBox 9: 10: public sub New 11: cboMyList.Location = new Point(75,50) 12: cboMyList.Text = "Wählen Sie ein Element aus" 13: cboMyList.Items.Add("Single Item") 14: cboMyList.Items.AddRange(new Object() {"Erster im Bereich", "zweiter im Bereich ", "Dritter im Bereich "}) 15: 16: Me.Text = "Listing 6.2" 17: Me.Controls.Add(cboMyList) 18: end sub 19: 20: public shared sub Main() 21: Application.Run(new Listing62) 22: end sub 23: end class 24: end namespace
184
Das Steuerelement ComboBox
Wir fangen an, indem wir in den Zeilen 13 und 14 Elemente hinzufügen (gleich mehr dazu). Zeile 12 legt den Text fest, der im Textfeld des Steuerelements angezeigt wird. Dieser Text erscheint, falls kein anderes Element der Liste ausgewählt wurde. Auf diese Weise fungiert er stellvertretend als Standardwert oder -auswahl. Zeile 13 fügt der Liste mit Hilfe der Add-Methode ein einzelnes Element hinzu. Zeile 14 fügt der ComboBox ein Array hinzu. Sowohl die Add- als auch die AddRange-Methode übernehmen Object-Datentypen als Parameter. Hier erzeugen Sie Objekte vom Typ string, dem gebräuchlichsten Typ, doch Sie können leicht andere Typen hinzufügen. Wenn Sie der ComboBox wirklich einen anderen Objekttyp hinzufügen, dann stellen Sie bitte sicher, dass die Eigenschaft DisplayMember der Eigenschaft des Objekts entspricht, das Sie anzeigen wollen. So zum Beispiel die Text-Eigenschaft eines Button-Objekts: cboMyList.Items.Add(new Button()) cboMyList.DisplayMember = "Text"
Wenn Sie jedoch keine Eigenschaft für die Anzeige angeben, führt die CLR automatisch die ToString-Methode am fraglichen Objekt aus und zeigt das Ergebnis an. Beachten Sie bitte, dass Zeile 14 ein neues Object-Array erzeugt und es in derselben Zeile mit Werten füllt. Sie hätten anstelle dieses Codes auch folgende Zeilen als Alternative verwenden können, um das Gleiche zu erzielen: dim arrList as Object() = {"erstes im Bereich", "zweites im Bereich", "drittes im Bereich"} cboMyList.Items.AddRange(arrList)
Die Wahl zwischen diesen beiden Vorgehensweisen ist Ihnen überlassen. Abbildung 6.3 zeigt das Ergebnis des Listings. Statt die AddRange-Methode zu verwenden, um mehrere Werte auf einmal hinzuzufügen, können Sie auch die DataSource-Eigenschaft auf ein Objekt-Array setzen. Das folgende Codestück bewirkt das Gleiche wie Zeile 14 in Listing 6.2: cboMyList.DataSource = new Object() {"erstes im Bereich", "zweites im Bereich", "drittes im Bereich"}
Mit Hilfe von DataSource können Sie die Elemente der ComboBox leicht auf jede beliebige Auflistung von Objekten setzen. An Tag 9 werden Sie eine weitere Einsatzmöglichkeit für diese Eigenschaft im Zusammenhang mit Datenbanken kennen lernen. Wenn Sie der ComboBox mit Hilfe der Add-Methode viele Elemente hinzufügen, rufen Sie als Erstes die BeginUpdate-Methode auf: cboMyList.BeginUpdate()
185
Windows Forms mit Steuerelementen erweitern
Diese Methode zwingt die Anwendung, mit dem Aktualisieren der Oberfläche der ComboBox so lange zu warten, bis alle Elemente hinzugefügt worden sind. Das Aktualisieren der Benutzeroberfläche ist ein zeitraubender Vorgang, und wenn Sie viele Elemente auf einmal hinzuzufügen haben, könnten die Aktualisierungen Ihre Anwendung praktisch zum Stillstand bringen. Wenn Sie mit dem Hinzufügen von Elementen fertig sind, rufen Sie die EndUpdate-Methode auf: cboMyList.EndUpdate()
Abbildung 6.3: Hier können Sie das Einzelelement ebenso sehen wie die Bereichselemente, die wir unserem Beispiel für das ComboBoxSteuerelement hinzugefügt haben.
Die Anzeige des Steuerelements steuern DropDownStyle ist die erste Eigenschaft, die Sie kennen müssen, um die Anzeige der ComboBox zu steuern. Diese Eigenschaft kontrolliert das Aussehen und die Bedienung des Steuerelements und kann die Art und Weise der Interaktion des Benutzers mit der ComboBox ändern. Diese Eigenschaft muss auf einen der Werte der ComboBoxStyle-Aufzählung gesetzt
sein: 쐽
DropDown: Das ist der Standardwert. Der Benutzer kann den Textbereich bearbeiten und muss einen Pfeil anklicken, um die Dropdown-Inhalte ansehen zu können.
쐽
DropDownList: Der Benutzer kann den Textbereich nicht bearbeiten. Der DropdownBereich ist der gleiche wie beim DropDown-Wert.
쐽
Simple: Der Benutzer kann den Textbereich bearbeiten und die Dropdown-Liste wird
stets angezeigt (es gibt also keinen anzuklickenden Pfeil, um die Liste aufzuklappen, obwohl eine Bildlaufleiste vorhanden sein kann). Sobald Sie diese Eigenschaft festgelegt haben, gibt es eine Reihe weiterer Eigenschaften, mit denen sich das Aussehen des Textbereichs anpassen lässt. MaxLength legt die Anzahl derjenigen Zeichen fest, die maximal in das Textfeld eingegeben werden können (wenn
186
Das Steuerelement ComboBox
aber der Benutzer ein Element aus der Liste auswählt, das mehr Zeichen hat als MaxLength erlaubt, wird trotzdem der Gesamttext des Elements angezeigt). SelectedText gibt immer den momentan im Textbereich markierten Wert zurück. Um zu steuern, welcher Teil des angezeigten Textes markiert wird, verwenden Sie die Eigenschaften SelectionStart und SelectionLength. Die erste übernimmt einen Integer, der das Zeichen angibt, mit dem die Markierung beginnt. Die zweite Eigenschaft bestimmt, wie viele Zeichen nach der SelectStart-Position markiert werden. Die Methoden Select und SelectAll erlauben Ihnen, einen Teil oder die Gesamtheit des Textes im Textbereich zu markieren. SelectAll übernimmt keine Parameter, aber Select übernimmt zwei Integerwerte, die den zu markierenden Zeichenbereich angeben. Text zu markieren ist häufig nützlich, wenn es notwendig ist, Elemente in der ComboBox zu aktualisieren oder neue einzufügen. Auch die Dropdown-Liste verfügt über mehrere eigene Eigenschaften. DroppedDown zeigt an, ob die Dropdown-Liste gerade angezeigt wird. DropDownWidth gibt ähnlich wie MaxLength die Anzahl der Zeichen an, die in der Liste angezeigt werden können. Die Maßeinheit lautet hier allerdings Pixel statt Zeichenzahl. MaxDropDownItems gibt die Anzahl der Elemente an, die auf einmal angezeigt werden, falls der Benutzer auf die Dropdown-Liste klickt. Wenn sich in der Liste mehr Elemente befinden als MaxDropDownItems, erscheint an der Seite der Liste eine Bildlaufleiste, um dem Benutzer zu gestatten, mehr Elemente anzusehen. Zwei Eigenschaften betreffen die Höhe des Kombinationsfeldes. ItemHeight gibt die Höhe jedes Listenelements in Pixeln an. Diese Eigenschaft erlaubt keine Anpassung der Elementhöhe, nur die Betrachtung der Elemente (es sei denn, Sie überschreiben das Standardverhalten des Steuerelements, worüber Sie an Tag 18 mehr erfahren). Der Wert der Eigenschaft PreferredHeight wird vom Betriebssystem des Benutzers zugewiesen und gibt die tatsächliche Höhe des Steuerelements an. Schließlich können Sie noch die Sorted-Eigenschaft nutzen, um der ComboBox zu befehlen, die Listenelemente automatisch (alphabetisch) zu sortieren. Dies gilt sowohl für vorhandene Elemente als auch für jedes neu hinzugefügte Element. Wollen Sie diese Funktion, dann setzen Sie Sorted auf True, wenn nicht, dann auf False.
Die ComboBox verwenden Da Sie nun dem Benutzer alle Werte in der ComboBox zur Auswahl stellen, müssen Sie wissen, wie Sie die Auswahl kontrollieren und überwachen. Zwei der gebräuchlichsten ComboBox-Mitglieder sind die SelectedIndex-Eigenschaft und das SelectedIndexChangedEreignis. Listing 6.3 zeigt ein Beispiel, in dem diese Mitglieder eingesetzt werden.
187
Windows Forms mit Steuerelementen erweitern
Listing 6.3: Die Auswahl, die der Benutzer getroffen hat, überwachen 1: Imports System 2: Imports System.Windows.Forms 3: Imports System.Drawing 4: 5: namespace TYWinforms.Day6 6: 7: public class Listing63 : Inherits Form 8: private cboMyList as new ComboBox 9: 10: public sub New 11: cboMyList.Location = new Point(75,50) 12: cboMyList.Text = " Wählen Sie ein Element!" 13: dim arrList as Object() = {"erstes im Bereich", "zweites im Bereich", "drittes im Bereich "} 14: cboMyList.Items.AddRange(arrList) 15: AddHandler cboMyList.SelectedIndexChanged, new EventHandler(AddressOf Me.HandleSelect) 16: 17: Me.Text = "Listing 6.3" 18: Me.Controls.Add(cboMyList) 19: end sub 20: 21: public sub HandleSelect(Sender as Object, e as EventArgs) 22: MessageBox.Show("Sie haben folgendes Element ausgewählt: " & cboMyList.SelectedIndex.ToString) 23: end sub 24: 25: public shared sub Main() 26: Application.Run(new Listing63) 27: end sub 28: end class 29: end namespace
Dieses Listing ähnelt dem Listing 6.2. Die ComboBox wird in den Zeilen 13 und 14 gefüllt, wobei diesmal nur ein Array benutzt wird. In Zeile 15 wird dann dem SelectedIndexChanged-Ereignis ein Delegat zugewiesen. Er tritt nur dann in Aktion, wenn der Benutzer ein neues Listenelement auswählt. Der Handler HandleSelect für dieses Ereignis beginnt in Zeile 21. Er besteht aus einer einzigen Codezeile. Mit Hilfe der Methode MessageBox.Show zeigen Sie dem Benutzer eine Meldung an, in der Sie angeben, welches Listenelement er ausgewählt hat. Denken Sie daran, dass alle Listen in .NET nullbasiert sind (d.h. die Zählung ihrer Elemente beginnt
188
Das Steuerelement DateTimePicker
bei 0 statt bei 1), so dass bei Auswahl des zweiten Elements durch den Benutzer das Meldungsfeld »Element 1« als ausgewählt zeigen wird. Abbildung 6.4 zeigt das Ergebnis, das Listing 6.3 produziert.
Abbildung 6.4: SelectedIndex zeigt die Indexnummer des ausgewählten
Listenelements an.
In den meisten Fällen werden Ihnen diese beiden Mitglieder genügend Kontrolle über das Steuerelement geben. Es gibt noch eine Reihe weiterer nützlicher Methoden: FindString und FindStringExact. Die erste Methode sucht und liefert den Index für das erste Listenelement, das mit der angegebenen Zeichenfolge beginnt. In Listing 6.3 würde folgendes Codestück die Zahl 0 zurückgeben: cboMyList.FindString("1st") FindStringExact hingegen gibt das erste Listenelement zurück, das der (gesuchten) Zei-
chenfolge exakt entspricht. Das obige Codestück würde nichts zurückgeben, weil es keine Elemente gibt, die nur den Text »1st« enthalten. Diese Methode erweist sich als nützlich, wenn man in einer ComboBox nach einem bestimmten Wert suchen muss. Insbesondere ist sie für den Benutzer von Vorteil, wenn die Liste recht umfangreich ist. Kurzum: Die ComboBox ist ein sehr leistungsfähiges Steuerelement; Sie müssen bloß wissen, wie Sie es einsetzen.
6.4
Das Steuerelement DateTimePicker
Gemäß der Voreinstellung verhält sich das Steuerelement DateTimePicker recht ähnlich wie die ComboBox; es verfügt über einen editierbaren Textbereich und eine aufklappbare Liste. Doch besteht der Textbereich aus einem änderbaren Datum und die »Liste« aus einem interaktiven Kalender. Abbildung 6.5 zeigt ein typisches DateTimePicker-Steuerelement. Dieses Steuerelement präsentiert uns eine sehr leistungsfähige Benutzeroberfläche. Der Benutzer kann im Steuerelement das Datum ändern, indem er es direkt in den Textbe-
189
Windows Forms mit Steuerelementen erweitern
reich einträgt oder indem er eines aus dem Kalenderbereich auswählt. Der Kalenderbereich verfügt über Schaltflächen, mit deren Hilfe der Benutzer monatsweise vor und zurück springen kann. Er kann auch Monate auswählen, indem er nach einem Klick auf die Titelleiste aus der herunterklappenden Liste einen Monat auswählt. Das Beste aber kommt noch: Der DateTimePicker gestattet nur die Auswahl oder Eingabe von realen, gültigen Daten. Daher kann ein Benutzer niemals das Datum »57. Macomber 1345« eingeben, wie sehr er es auch versuchen mag.
Abbildung 6.5: Ein DateTimePicker stellt Ihnen Daten und Uhrzeiten zur Auswahl bereit.
Der DateTimePicker weist eine Reihe von Eigenschaften auf, die sich zur Anpassung des Kalenderaussehens nutzen lassen: Eigenschaft
Wird zur Einstellung von x benutzt
CalendarFont
die im Kalender benutzte Schriftart
CalendarForeColor
die Farbe des Textes
CalendarMonthBackground
die Farbe hinter dem Monat
CalendarTitleBackColor
die Farbe hinter dem Titel
CalendarTitleForeColor
die Farbe des Titeltextes
CalendarTrailingForeColor
die Farbe der im Kalender nachfolgenden Datumswerte (die nicht mehr zum aktuellen Monat gehören)
Tabelle 6.1: So gestalten Sie den Kalender.
Sie können das Erscheinungsbild ganz Ihren Wünschen anpassen. Die Format-Eigenschaft erlaubt Ihnen zudem, die Anzeigeform der Daten im Textbereich zu steuern. Diese müssen die Werte aus der DateTimePickerFormat-Aufzählung aufweisen: 쐽
Custom: ein angepasstes Anzeigeformat; verwenden Sie die CustomFormat-Eigenschaft.
쐽
Long: das vom Betriebssystem des Benutzers ausgewählte lange Datumsformat. Zum Beispiel »Mittwoch, 7. November 2001«.
190
Das Steuerelement DateTimePicker
쐽
Short: das vom Betriebssystem des Benutzers ausgewählte kurze Datumsformat. Zum Beispiel »7.11.2001«
쐽
Time: Die vom Betriebssystem des Benutzers vorgenommene Formatierung der Zeitangabe. Zum Beispiel »9:48:25«
Falls das Format auf DateTimePickerFormat.Custom eingestellt ist, können Sie die CustomFormat-Eigenschaft dazu verwenden, die Art und Weise anzupassen, wie Datum und Zeit angezeigt werden. Diese Eigenschaft benutzt speziell formatierte Zeichenfolgen, um Datums- und Zeitangaben darzustellen. Das folgende Codestück würde »01. Juni, 0930« anzeigen: dtpMyDate.CustomFormat = "dd. MMMM, HHmm"
Tabelle 6.2 führt alle verfügbaren Zeichenfolgen auf (Achtung: bei den Zeichenfolgenwerten ist die Groß-/Kleinschreibung zu berücksichtigen). Formatzeichenfolge
Beschreibung
d
Tagesangabe mit einer oder zwei Ziffern
dd
Tagesangabe mit zwei Ziffern
ddd
Drei-Zeichen-Abkürzung für Wochentag
dddd
Vollständiger Wochentagsname
h
Ein- oder Zwei-Ziffern-Ausgabe der Stunde im 12-Stunden-Format (z.B. 1 p.m.)
hh
Zwei-Ziffern-Ausgabe der Stunde im 12-Stunden-Format
H
Ein- oder Zwei-Ziffern-Ausgabe der Stunde im 24-Stunden-Format (z.B. 1300 Uhr)
HH
Zwei-Ziffern-Ausgabe der Stunde im 24-Stunden-Format
m
Ein- oder Zwei-Ziffern-Ausgabe der Minute
mm
Zwei-Ziffern-Ausgabe der Minute
M
Ein- oder Zwei-Ziffern-Ausgabe des Monats
MM
Zwei-Ziffern-Ausgabe des Monats
MMM
Drei-Zeichen-Abkürzung für Monat
MMMM
Vollständiger Monatsname
Tabelle 6.2: Datum- und Zeit-relevante Zeichenfolgen für CustomFormat
191
Windows Forms mit Steuerelementen erweitern
Formatzeichenfolge
Beschreibung
s
Ein- oder Zwei-Ziffern-Ausgabe der Sekunde
ss
Zwei-Ziffern-Ausgabe der Sekunde
t
Einbuchstabige Abkürzung für AM/PM (z.B. wird »AM« als »A« angezeigt)
tt
Zweibuchstabige Abkürzung für AM/PM
y
Ein- oder Zwei-Ziffern-Ausgabe der Jahreszahl (z.B. wird »2001« als »1« angezeigt)
yy
Die letzten beiden Ziffern der Jahreszahl
yyyy
Alle vier Ziffern der Jahreszahl
Tabelle 6.2: Datum- und Zeit-relevante Zeichenfolgen für CustomFormat (Forts.)
Die Value-Eigenschaft gibt ein DateTime-Objekt zurück, das die gerade im Steuerelement angezeigte Zeit zurückgibt. MaxDate und MinDate geben die maximalen (am weitesten in der Zukunft liegenden) und minimalen (am weitesten in der Vergangenheit liegenden) Daten zurück, die der Benutzer aus dem Kalender auswählen kann. Diese Eigenschaften übernehmen DateTime-Objekte als Werte. Das folgende Codestück setzt das MinimumDatum auf den 20. Juni 1985 fest: dtpMinDate = new DateTime(1985, 6, 20) DropDownAlignment übernimmt einen Wert aus der LeftRightAlignment-Aufzählung, um anzugeben, nach welcher Seite des Textbereichs der Kalender ausgerichtet sein soll. Mögliche Werte dafür sind LeftRightAlignment.Left (Standardwert) und LeftRightAlignment.Right.
Es gibt eine Reihe von Möglichkeiten, wie Sie dem Benutzer gestatten können, das Datum im Textbereich zu ändern. Bei der ersten Version (Standardeinstellung) kann der Benutzer den Wert eingeben. Der DateTimePicker erlaubt nur gültige Einträge (der genaue Typ kann von der Format-Eigenschaft abhängen), daher müssen Sie sich nicht um ungültige Daten kümmern. Die zweite Methode erlaubt es dem Benutzer, ein Auf-Ab-Steuerelement zu verwenden (in der Art einer kleinen Bildlaufleiste), um die Werte zu ändern. Für diese Methode müssen Sie die ShowUpDown-Eigenschaft auf True setzen. Setzt man ShowCheckBox auf True, wird ein Kontrollkästchen neben dem Datums im Textbereich angezeigt. Dadurch kann der Benutzer signalisieren, dass er ausdrücklich das angezeigte Datum auswählt. Das kann sich als nützlich erweisen, wenn Sie vom Benutzer verlangen, dass er einen Datumsbereich angibt. Indem er das Kontrollkästchen deaktiviert lässt, könnte der Benutzer ein Open-End- oder ein Startdatum angeben. Verwenden Sie die Checked-Eigenschaft, um herauszufinden, ob das Kontrollkästchen gerade angekreuzt ist.
192
Das Steuerelement ImageList
Es gibt eine paar wichtige Ereignisse im Steuerelement DateTimePicker. ValueChanged wird ausgelöst, sobald sich der Wert im Textbereich ändert, ob nun durch direkte Bearbeitung oder durch eine Auswahl aus dem Kalender. Die CloseUp- und DropDown-Ereignisse finden statt, sobald der Kalenderbereich des Steuerelements geschlossen respektive geöffnet wird. Sie könnten diese Ereignisse ausnutzen, um Gültigkeitsprüfungen auszuführen, etwa um zu sehen, ob ein ausgewähltes Datum in einen gültigen Bereich fällt, bevor Sie dem Benutzer erlauben fortzufahren.
6.5
Das Steuerelement ImageList
Wir haben das ImageList-Steuerelement bereits an Tag 4 besprochen, so dass wir uns damit nicht allzu lange aufhalten wollen. Die herausragende Eigenschaft von ImageList ist Images, welche eine Auflistung von Grafiken aufnimmt, die sich von anderen Teilen Ihrer Anwendung nutzen lassen. Um Bilder hinzuzufügen, verwenden Sie einfach die AddMethode, etwa so: private ImageList ilstToolbar = new ImageList(); ilstToolbar.Images.Add(Image.FromFile("i_open.bmp ")); ilstToolbar.Images.Add(Image.FromFile("i_maximize.bmp "));
Um Bilder zu entfernen, verwenden Sie Remove, wobei Sie den Index des zu entfernenden Bildes angeben. Es gibt nur wenige weitere Eigenschaften: ColorDepth, die zu jedem gelisteten Bild dessen Anzahl an Farben angibt (ausgedrückt als Bit-Wert: 4 Bit, 8 Bit usw.); ImageSize, die sich für die Größenänderung der Bilder einsetzen lässt; und schließlich TransparentColor, welche eine Farbe im Bild durchsichtig machen kann, wodurch Dinge hinter dem Bild zum Vorschein kommen. Listing 6.4 zeigt ein typisches Beispiel einer ImageList, die mit einem Button-Steuerelement interagiert. Listing 6.4: Die Verwendung von ImageList in C# 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12:
using System; using System.Windows.Forms; using System.Drawing; namespace TYWinForms.Day6 { public class Listing64 : Form { private ImageList ilstButton = new ImageList(); private Button btImage = new Button(); public Listing64() { ilstButton.Images.Add(Image.FromFile("i_open.bmp"));
193
Windows Forms mit Steuerelementen erweitern
13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37:
ilstButton.Images.Add(Image.FromFile("i_maximize.bmp")); ilstButton.Images.Add(Image.FromFile("i_happy.bmp")); btImage.ImageList = ilstButton; btImage.ImageIndex = 0; btImage.Location = new Point(100,100); btImage.Click += new EventHandler(this.CycleImages); this.Text = "Listing 6.4"; this.Controls.Add(btImage); } public void CycleImages(Object Sender, EventArgs e) { if (btImage.ImageIndex == ilstButton.Images.Count-1) { btImage.ImageIndex = 0; } else { btImage.ImageIndex += 1; } } public static void Main() { Application.Run(new Listing64()); } } }
Die ImageList wird in den Zeilen 12 bis 14 mit Bildern gefüllt (diese Zeilen sollten auf tatsächlich vorhandene Bilder zeigen). Button verfügt wie viele Windows Forms-Steuerelemente über eine ImageList-Eigenschaft, die die Zuweisung einer ImageList zu diesem Steuerelement zulässt. Zeile 16 weist die ImageList dem Button-Steuerelement zu. Zeile 17 stellt die Schaltfläche so ein, dass sie zunächst das erste Bild in der Liste anzeigt. Zeile 18 positioniert die Schaltfläche auf dem Formular und Zeile 19 fügt dem Click-Ereignis einen Delegaten hinzu. Der Ereignishandler CycleImages (Zeile 25) ändert das auf der Schaltfläche angezeigte Bild, führt aber zuvor Prüfungen durch. Klickt der Benutzer auf die Schaltfläche, soll das nächste Bild angezeigt werden. Dies wird erreicht, indem einfach die ImageIndex-Eigenschaft nach oben gezählt (inkrementiert) wird (Zeile 29). Sobald man jedoch beim letzten Bild in der Liste angelangt ist, ändert sich das Bild mangels Vorrat nicht mehr. In diesem Fall setzt man den ImageIndex auf 0 zurück, so dass der Hochzählzyklus von neuem beginnen kann. In Zeile 26 wird eine Prüfung durchgeführt, um herauszufinden, ob der aktuelle Wert ImageIndex-Wert bereits auf dem letzten Listenwert (Count –1) angelangt ist. Ist das der Fall, startet ImageIndex wieder auf 0, andernfalls wird auf den nächsten Wert weitergezählt.
194
Das Steuerelement ListBox
Abbildung 6.6 zeigt das Ergebnis dieses Code-Beispiels.
Abbildung 6.6: Weist man einem Button eine ImageList zu, führt dies zur Anzeige von Grafiken statt Text auf der Schaltfläche.
6.6
Das Steuerelement ListBox
Das ListBox-Steuerelement (Listenfeld) ähnelt dem ComboBox-Steuerelement (Letzteres besteht aus einer Kombination von ListBox und TextBox). Ihnen gemeinsam sind solche Mitglieder wie Items, DataSource, DisplayMember, SelectedIndex, FindString usw. Sowohl ListBox als auch ComboBox sind von derselben Basisklasse abgeleitet, nämlich ListControl. Das die ListBox keine Kombination von Elementen darstellt, ist sie weitaus spezialisierter in ihren Aufgaben als die ComboBox. Daher verfügt sie über einige Mitglieder mehr. Mit Hilfe der ListBox lassen sich dem Benutzer mehrere Elemente in einer Liste anzeigen. Eine ListBox ist stets sichtbar, denn sie wird nicht wie die Dropdown-Liste in der ComboBox ausgeblendet. Und anders als dort kann der Benutzer in der ListBox mehrere Elemente auf einmal auswählen, indem er dabei einfach die (Strg)- oder (ª)-Taste gedrückt hält. Dieses Verhalten der ListBox bedingt auch eine komplexere Funktionalität mit sich. Das Beispiel in Listing 6.5 verwendet fast alle verfügbaren Eigenschaften von ListBox. Listing 6.5: Die Verwendung des ListBox-Steuerelements mit C# 1: 2: 3: 4: 5: 6: 7: 8: 9: 10:
using System; using System.Windows.Forms; using System.Drawing; namespace TYWinForms.Day6 { public class Listing65 : Form { private ListBox lbItems = new ListBox(); public Listing65() {
195
Windows Forms mit Steuerelementen erweitern
11: 12: 13: 14:
lbItems.Location = new Point(75,75); lbItems.MultiColumn = true; lbItems.ColumnWidth = 75; lbItems.SelectedIndexChanged += new EventHandler(this.HandleSelect); lbItems.SelectionMode = SelectionMode.MultiExtended;
15: 16: 17:
Object [] arrColors = {"Weiss", "Rot", "Orange", "Gelb", "Grün", "Blau", "Indigo", "Violett", "Schwarz"}; lbItems.Items.AddRange(arrColors);
18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29:
this.Text = "Listing 6.5"; this.Controls.Add(lbItems); } public void HandleSelect(Object Sender, EventArgs e) { ListBox lbTemp = (ListBox)Sender; int i; if (lbTemp.SelectedIndices.Count == 1 ) { lbItems.BackColor = Color.FromName(lbTemp.SelectedItem.ToString()); } else { for (i = 0; i < lbTemp.SelectedIndices.Count; i++) { MessageBox.Show("Sie haben folgendes Element ausgewählt: " + lbTemp.SelectedItems[i].ToString()); } }
30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41:
} public static void Main() { Application.Run(new Listing65()); } } }
Die Zeilen 11 bis 18 enthalten die meisten für uns interessanten Eigenschaften. Zeile 11 kennen Sie ja bereits; sie legt die Position des Steuerelements im Formular fest. Die ListBox kann mehrere Spalten von Elementen zugleich anzeigen. Wenn Sie MultiColumn wie in Zeile 12 auf true setzen, erzeugt die ListBox so viele Spalten wie nötig sind, um ihre Elemente aufzunehmen, damit der Benutzer keinen Bildlauf nach unten ausführen muss. (Beachten Sie, dass diese Bildlauffunktion bereits im ListBox-Steuerelement eingebaut ist; daher müssen Sie sich nicht um die betreffenden Ereignisse kümmern.) Die Abbildung 6.7 zeigt ein Beispiel für dieses Konzept.
196
Das Steuerelement ListBox
Multicolumn = true; (Spalten werden auf angemessene Weise erzeugt, so dass vertikales Scrollen nicht nötig ist) Element 1 Element 6 Element 2 Element 7 Element 3 Element 8 Element 4 Element 5
Multicolumn = false; (Voreinstellung) (Der Benutzer muss vertikal scrollen, um alle Elemente sehen zu können)
Element 1 Element 2 Element 3 Element 4 Element 5 Element 6 Element 7 Element 8
Abbildung 6.7: MultiColumn gibt an, ob sich Listenelemente horizontal oder vertikal scrollen lassen.
Zeile 13 legt einfach die Breite jeder Spalte fest, sofern MultiColumn auf true gesetzt ist. Zeile 14 weist dem SelectedIndexChanged-Ereignis einen Delegaten zu, genau wie beim gleichnamigen Ereignis in der ComboBox. Die Zeile 15 legt den SelectionMode der ListBox fest. Diese Eigenschaft bestimmt, wie der Benutzer Elemente auswählen kann (Mehrfach- oder Einzelauswahl usw.). Die möglichen Werte der SelectionMode-Aufzählung sind: 왘
MultiExtended: Der Benutzer kann mehrere Elemente mit Hilfe der (ª)-, (Strg)- und Pfeiltasten auswählen. (Dies ist die übliche Vorgehensweise,
um Mehrfachauswahl zu gestatten.) 왘
MultiSimple: Der Benutzer kann mehrere Elemente auswählen, indem er
auf eines nach dem anderen klickt. Jeder weitere Klick nimmt ein neues Element in die bestehende Auswahl auf, daher ist keine Zusatztaste wie etwa (ª) oder (Strg) nötig. 왘
None: Es können keine Elemente ausgewählt werden.
왘
One: Es kann nur ein Element ausgewählt werden.
In den Zeilen 17 und 18 erstellen Sie schließlich ein Array von Zeichenfolgenobjekten und weisen es der ListBox mit Hilfe der AddRange-Methode zu. Beachten Sie, dass wir hier Farbennamen verwenden. Gleich werden Sie sehen, wie diese Farben eingesetzt werden. Springen wir hinunter in Zeile 24: Hier ist der Ereignishandler für das Ereignis SelectedIndexChanged zu finden. Zunächst sind in dieser Methode zwei Variablen zu erstellen: eine ListBox und ein Integer. Die erste Variable wird als
Abkürzung zum Objekt verwendet, das das Ereignis erzeugt hat. Denken Sie daran, dass die Sender-Variable zuerst in den richtigen Typ zu konvertieren ist, um sie benutzen zu können: ((ListBox)Sender).eigenschaft
Um Ihnen zu ersparen, den Typ jedes Mal konvertieren zu müssen, tun Sie das einmal in Zeile 25 und verwenden ab jetzt die neue lbTemp-Variable, die nun
197
Windows Forms mit Steuerelementen erweitern
identisch mit dem Objekt lbItems ist, das dieses Ereignis generiert hat. Dieser Vorgang ist zwar nicht unbedingt nötig, erspart aber einiges an Tipparbeit. Es gibt zwei mögliche Situationen: Der Benutzer hat ein Element markiert, und der Benutzer hat mehr als ein Element markiert. Im Einzelelement-Fall wollen wir die Hintergrundfarbe der ListBox auf die gewählte Farbe einstellen. In der Mehrfachelement-Situation können wir offensichtlich den Hintergrund der ListBox nicht auf mehrere Farben gleichzeitig setzen; statt dessen zeigen wir einfach eine Meldung an, die den Benutzer über seine Auswahl informiert (eigentlich wird nur seine letzte Auswahl in der Meldung angezeigt). In Zeile 28 benutzen Sie die SelectedIndices-Eigenschaft, die die Indizes der ausgewählten Elemente zurückgibt, ganz gleich, wie viele Elemente ausgewählt worden sind. Sollte die Anzahl der Indizes in SelectedIndices (wird durch die Count-Eigenschaft angegeben) gleich 1 sein, dann wissen Sie, dass nur ein Element ausgewählt wurde. In Zeile 29 verwenden Sie dann die FromName-Methode des Color-Objekts, um den richtigen Color-Wert je nach dem ausgewählten Element zurückzugeben. Denken Sie daran, dass die SelectedItem-Eigenschaft ein String-Objekt liefert, so dass Sie die ToString-Methode verwenden müssen, um statt dessen die Zeichenfolgen-Darstellung zu erhalten. Die Zeilen 30 bis 33 behandeln jenen Fall, bei dem es mehr als eine Auswahl gibt. Mit Hilfe der Integer-Variablen i, die Sie zuvor erzeugt haben, führen Sie eine Programmschleife durch jedes der ausgewählten Elemente aus. Die forSchleife setzt die Variable i auf 0 und fährt fort, sie zu inkrementieren (und den Code in der Schleife auszuführen), bis sie nicht mehr kleiner als die Anzahl der ausgewählten Indizes ist (welche durch SelectedIndices.Count angegeben wird). Der Code in der Programmschleife verwendet die MessageBox.ShowMethode, um eine Meldung an den Benutzer auszugeben. SelectedItems ist eine Auflistung der ausgewählten Elemente (ähnlich wie SelectedIndices eine Auflistung der Indexzahlen der ausgewählten Elemente ist). Das war's schon. Abbildung 6.8 zeigt das Ergebnis, nachdem eine einzelne Farbe ausgewählt wurde (die Hintergrundfarbe der ListBox hat sich geändert) und dann mehrere Elemente. Es gibt noch ein paar weitere Mitglieder der ListBox kennen zu lernen. ScrollAlwaysVisible lässt sich auf true setzen, damit die ListBox ständig eine senkrechte Bildlaufleiste anzeigt, selbst wenn alle Elemente auch ohne Bildlauf sichtbar wären. UseTabStops ist eine interessante Eigenschaft. Falls das Element oder der Text, den Sie in der ListBox anzeigen wollen, Tabulatorzeichen enthält, veranlasst das Setzen dieser Eigenschaft auf true die normale Anzeige dieser Tabulatoren. Ist sie jedoch auf false gesetzt,
werden die Tabulatorzeichen zu Leerzeichen umgewandelt.
198
Das Steuerelement PictureBox
Abbildung 6.8: Die ListBox gestattet die Mehrfachauswahl.
Die ListBox verfügt über eine Text-Eigenschaft, die sich von den Text-Eigenschaften aller anderen Steuerelemente unterscheidet. Wurde ein Element bereits ausgewählt, dann gibt Text den Text dieses Elements zurück. Wurden mehrere Elemente ausgewählt, gibt Text den Text des ersten ausgewählten Elements zurück. Wenn Sie jedoch versuchen, Text gleich einem Wert zu setzen, dann sucht es diesen Text und wählt dieses Element genau wie die FindStringExact-Methode aus. Der folgende Code würde beispielsweise das Element mit dem Text »Hallo Welt« auswählen (vorausgesetzt, es gibt ein Element mit diesem Text): lbTemp.Text = "Hallo Welt";
6.7
Das Steuerelement PictureBox
Das Steuerelement PictureBox ist sehr einfach. Seine einzige Aufgabe besteht in der Anzeige einer Grafik. Werfen wir einen Blick auf seine Eigenschaften. Die wahrscheinlich wichtigste ist wohl die Image-Eigenschaft. Sie gibt an, welches Bild die PictureBox anzeigen wird. Bei diesem Bild kann es sich um praktisch jede Grafikdatei handeln, so etwa im GIF-, JPG- oder BMP-Format. Zum Beispiel: dim pbMyPicture as new PictureBox pbMyPicture.Image = Image.FromFile("i_happy.bmp")
Stellen Sie auf jeden Fall sicher, dass das zu verwendende Bild eine gültige, also vorhandene Datei ist und Sie dafür die richtige Pfadangabe bereitstellen (Grafikdateien werden üblicherweise im selben Ordner wie die Anwendung gespeichert). SizeMode steuert, wie die Grafiken angezeigt werden und nimmt einen der folgenden Werte der PictureBoxSizeMode-Aufzählung an:
199
Windows Forms mit Steuerelementen erweitern
쐽
AutoSize: Die PictureBox passt sich an die Größe der enthaltenen Grafik an.
쐽
CenterImage: Das Bild wird in der Mitte der PictureBox angezeigt. Ist das Bild größer als die PictureBox, werden seine äußeren Ränder abgeschnitten.
쐽
Normal: Das Bild wird an der linken oberen Ecke der PictureBox ausgerichtet. Ist das Bild größer als die PictureBox, werden seine äußeren Ränder abgeschnitten.
쐽
StretchImage: Das Bild wird entweder gestaucht oder gestreckt, um die PictureBox
vollständig auszufüllen. An Tag 13 erfahren Sie, wie man Bildlaufleisten in Verbindung mit der PictureBox einsetzt, um in einem Bild zu navigieren, das größer als die PictureBox ist.
6.8
Das Steuerelement TabControl
Im Betriebssystem Windows findet man häufig Fenstern, die in der Art von Karteikarten gestaltet sind – Fenster mit mehreren so genannten Registerkarten (auch als Tabs oder Tabseiten bezeichnet), die bestimmte Funktionen gruppieren. Abbildung 6.9 beispielsweise zeigt das Fenster EIGENSCHAFTEN VON ANZEIGE in Windows, das über fünf Registerkarten verfügt. Ähnlich wie die ToolBar- und MainMenu-Steuerelemente besteht auch TabControl aus dem Hauptsteuerelement und mehreren untergeordneten Steuerelementen. Jede Tabseite wird durch ein TabPage-Steuerelement dargestellt. Um dem TabControl eine neue Tabseite hinzuzufügen, ruft man einfach die Add-Methode auf: TabControl tcMain = new TabControl(); tcMain.TabPages.Add(new TabPage("Tab 1");
Diese Steuerelemente sind am einfachsten mit Hilfe eines Beispiels zu verstehen. Es ist ein recht umfangreiches Beispiel, weswegen ich es auf mehrere Abschnitte aufteilen werde, um die Untersuchung zu erleichtern. Der erste Teil, das Anwendungsgerüst, ist in Listing 6.6 zu sehen. Listing 6.6: So erzeugen Sie in C# Registerkarten für Ihre Anwendung. 1: 2: 3: 4: 5: 6: 7:
200
using System; using System.Windows.Forms; using System.Drawing; namespace TYWinForms.Day6 { public class Listing66 : Form {
Das Steuerelement TabControl
8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27:
private TabControl tcMain = new TabControl(); private TabPage tpGeneral = new TabPage("Allgemein"); private TabPage tpDisplay = new TabPage("Anzeige"); private TabPage tpLocation = new TabPage("Position"); private TabPage tpAdvanced = new TabPage("Extras"); private private private private private private private private
Label lbl1Name = new Label(); TextBox tb2Color = new TextBox(); Button bt2Color = new Button(); TextBox tb3Top = new TextBox(); TextBox tb3Left = new TextBox(); Button bt3Location = new Button(); TextBox tb4Tab = new TextBox(); Button bt4Tab = new Button();
public static void Main() { Application.Run(new Listing66()); } } }
Abbildung 6.9: Mit dem Steuerelement TabControl lassen sich Fenster mit Registerkarten erstellen.
201
Windows Forms mit Steuerelementen erweitern
Dieses Listing ist sehr einfach. Die passenden Namensräume werden in den Zeilen 1 bis 3 importiert. Die Klasse und die Namensräume werden in den Zeilen 5 und 7 deklariert und alle benötigten Steuerelemente in den Zeilen 8 bis 21. Zu diesen gehören das TabControl, vier TabPage- und diverse Button- und Label-Steuerelemente. Zeile 23 deklariert die Methode Main, die wie üblich lediglich die Run-Methode aufruft. Listing 6.7 zeigt den Konstruktor für diese Klasse, der zwischen die Zeilen 22 und 23 des Listings 6.6 platziert werden sollte. Listing 6.7: Beispiel für einen TabControl-Konstruktor 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34:
202
public Listing66() { // Tabseite 1 lbl1Name.Text = "Listing 6.6: Windows Forms in 21 Tagen"; lbl1Name.AutoSize = true; lbl1Name.Location = new Point(10,75); // Tabseite 2 tb2Color.Location = new Point(25,75); bt2Color.Text = "Ändern!"; bt2Color.Location = new Point(125,75); bt2Color.Click += new EventHandler(this.ChangeColor); // Tabseite 3 tb3Top.Text = this.DesktopLocation.Y.ToString(); tb3Top.Location = new Point(25,50); tb3Left.Text = this.DesktopLocation.X.ToString(); tb3Left.Location = new Point(25,75); bt3Location.Text = "Ändern!"; bt3Location.Location = new Point(125,75); bt3Location.Click += new EventHandler(this.ChangeLocation); // Tabseite 4 tb4Tab.Location = new Point(25,50); bt4Tab.Text = "Neuer Tab!"; bt4Tab.Location = new Point(75,75); bt4Tab.Click += new EventHandler(this.AddTab); tpGeneral.Controls.Add(lbl1Name); tpDisplay.Controls.Add(tb2Color); tpDisplay.Controls.Add(bt2Color); tpLocation.Controls.Add(tb3Top);
Das Steuerelement TabControl
35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49:
tpLocation.Controls.Add(tb3Left); tpLocation.Controls.Add(bt3Location); tpAdvanced.Controls.Add(tb4Tab); tpAdvanced.Controls.Add(bt4Tab); tcMain.Width = this.Width; tcMain.Height = this.Height; tcMain.TabPages.Add(tpGeneral); tcMain.TabPages.Add(tpDisplay); tcMain.TabPages.Add(tpLocation); tcMain.TabPages.Add(tpAdvanced); this.Text = "Listing 6.6"; this.Controls.Add(tcMain); }
Dieser Teil des Codes ist in Abschnitte für jede Registerkarte aufgeteilt. Unser Entwurf erfordert vier Registerkarten: »Allgemein«, die allgemeine Informationen zur Anwendung anzeigt; »Anzeige«, die dem Benutzer die Anpassung der Farben der Anwendung erlaubt; »Position«, die dem Benutzer erlaubt, die Position der Anwendung auf dem Desktop mit Programmschritten anzupassen; und schließlich »Extras«, die dem Benutzer das Hinzufügen weiterer Tabseiten zur Anwendung gestattet. Die Zeilen 2 bis 5 richten die Steuerelemente ein, die auf der ersten Tabseite erscheinen werden. Hier haben wir lediglich ein Label-Steuerelement. In den Zeilen 8 bis 12 findet die Initialisierung der zweiten Tabseite statt. Wir erzeugen eine TextBox, in der der Benutzer eine neue Farbe eintragen kann, sowie eine Schaltfläche, um den Wert zu bestätigen. (Der Ereignishandler ChangeColor ist unten in Listing 6.8 zu finden.) Die Tabseite 3, in den Zeilen 15 bis 22 zu finden, enthält zwei Textfelder (eines für den oberen Rand der Anwendung und eines für den linken) und eine Schaltfläche, um die neuen Werte übernehmen zu können. Tabseite Nummer 4 weist ein Textfeld und eine Schaltfläche auf. (Beide Ereignishandler für diese Steuerelemente finden sich unten in Listing 6.8.) Nichts Aufregendes also bis jetzt. Als Nächstes müssen wir die Steuerelemente den passenden Tabseiten hinzufügen. Dies erfolgt ähnlich wie das Hinzufügen von Steuerelementen zu einem Formular: mit Hilfe der Controls.AddMethode. Die Zeilen 31 bis 38 fügen alle vorgestellten Steuerelemente in die entsprechenden Tabseiten ein. Die Zeilen 40 bis 45 stellen die Eigenschaften für das zentrale TabControlObjekt ein. Nun müssen wir alle Tabseiten dem TabControl hinzufügen, analog dem Einfügen von Steuerelementen in Tabseiten. In Zeile 48 müssen Sie das TabControl dem Form hinzufügen.
203
Windows Forms mit Steuerelementen erweitern
Sie können die Anwendung bereits in dieser Form kompilieren (vergessen Sie jedoch nicht, zuvor die Zeilen 12, 22 und 29 in Listing 6.7 auszukommentieren – diese Zeilen würden Fehler verursachen, da wir die entsprechenden Ereignishandler noch nicht erstellt haben). Beachten Sie, dass die Registerkarten bereits voll funktionsfähig sind: Sie können darin navigieren, ohne dafür extra Code geschrieben zu haben! Wir wollen die Ereignishandler aus Listing 6.8 näher untersuchen. Dieser Code sollte nach dem Konstruktor eingefügt werden, jedoch erst hinter der Main-Methode aus Listing 6.6 (beachten Sie, dass der Code nicht unbedingt hier stehen muss; das ist lediglich ein Vorschlag). Listing 6.8: Ereignisverarbeitung mit dem TabControl 1: public void ChangeColor(Object Sender, EventArgs e) { 2: int i; 3: 4: for (i = 0; i < tcMain.TabPages.Count; i++) { 5: tcMain.TabPages[i].BackColor = Color.FromName(tb2Color.Text); 6: } 7: } 8: 9: public void ChangeLocation(Object Sender, EventArgs e) { 10: this.DesktopLocation = new Point(Convert.ToInt32 (tb3Top.Text), Convert.ToInt32(tb3Left.Text)); 11: } 12: 13: public void AddTab(Object Sender, EventArgs e) { 14: tcMain.TabPages.Add(new TabPage(tb4Tab.Text)); 15: }
Die Zeilen 1 bis 7 umfassen die ChangeColor-Methode. Sie ist dafür zuständig, die vom Benutzer angegebene Farbe zu übernehmen und sie auf die Applikation anzuwenden. Die Methode wird ausgeführt, sobald die bt2color-Schaltfläche auf der 2. Tabseite angeklickt wird. Das Ändern der Farbe ist jedoch nicht so einfach, wie es aussieht. Man könnte die BackColor-Eigenschaft des Formulars auf die neue Farbe umstellen, doch da die Tabseiten das gesamte Formular überdecken, würde man die Farbänderung nicht sehen. Daher muss man die Farbe der Tabseiten selbst ändern. Zeile 4 verwendet eine weitere for-Schleife, um von der ersten Tabseite bis zur letzten (wie durch TabPages.Count angegeben) zu zählen. In jeder TabPage im TabControl-Steuerelement setzen Sie die BackColor-Farbe auf die vom Benutzer angegebene. Verwenden Sie die FromName-Methode des Color-Objekts, um den eingegebenen Text in einen Color-Wert umzuwandeln.
204
Das Steuerelement TabControl
Die Zeilen 9 bis 11 enthalten die ChangeLocation-Methode, die das Formular auf der Benutzeroberfläche bewegt, wobei Werte aus der 3. Tabseite berücksichtigt werden. Diese Methode ist sehr einfach: Sie benutzen lediglich die Eigenschaft DesktopLocation des Form-Objekts, um das Formular auf den benutzerdefinierten Koordinaten zu positionieren. Denken Sie daran, den Text in den Textfeldern jeweils mit Hilfe der Convert.ToInt32-Methode zu einem Integer umzuwandeln, wie in Zeile 10 zu sehen. Die AddTab-Methode in Zeile 13 ist schließlich die einfachste von allen. Sie erstellt eine neue Tabseite und fügt sie dem TabControl-Objekt hinzu. Der Text, den der Benutzer in tb4Tab angibt, wird als Titel der Tabseite verwendet. Kompilieren Sie die vollständige Anwendung und experimentieren Sie mit dem Funktionsumfang. Abbildung 6.10 zeigt das Ergebnis, nachdem eine neue Tabseite hinzugefügt wurde.
Abbildung 6.10: Mit dem TabControl-Steuerelement können Sie dynamisch weitere Tabseiten erstellen.
Es gibt noch ein paar TabControl-Eigenschaften, die Sie kennen sollten. Das TabControl hat die gleichen SelectedIndex- und SelectedIndexChanged-Mitglieder wie die Steuerelemente ComboBox und ListBox, so dass Sie damit bereits umgehen können. Zudem besitzt es eine SelectedTab-Eigenschaft, die das TabPage-Objekt zurückgibt, das gerade ausgewählt ist. Anstelle von Text für den Titel der Tabseite, wie Sie ihn in den Listings 6.6 bis 6.8 verwendet haben, kann TabControl auch Grafiken verwenden. Erstellen Sie einfach wie immer eine ImageList und weisen Sie sie der ImageList-Eigenschaft des TabControl-Steuerelements zu. Mit Hilfe der Alignment-Eigenschaft können Sie die Positionierung der Tabseiten im Formular ändern. Diese Eigenschaft übernimmt einen der Werte aus der TabAlignment-Aufzählung: Bottom, Left, Right oder Top. Eine kleine Änderung, die Sie hier vornehmen, kann Ihrer Anwendung ein völlig neues Aussehen verleihen.
205
Windows Forms mit Steuerelementen erweitern
Auch das TabControl verfügt über eine Appearance-Eigenschaft. Sie verwendet Werte aus der TabAppearance-Aufzählung und bestimmt, wie die einzelnen Tabseiten angezeigt werden sollen. Mögliche Werte sind Buttons, FlatButtons und Normal. Die ersten beiden Optionen vermitteln den visuellen Eindruck von Schaltflächen statt Registern, doch die Funktionalität ist die gleiche. Einige der Eigenschaften betreffen die Größe der Registerkarten. ItemSize holt oder setzt die Breite und Höhe der Registerkarten. In der Standardeinstellung passt sich die Registerkarte automatisch ihrem Inhalt an. Die Padding-Eigenschaft ändert den Abstand zwischen den Elementen auf der Tabseite und deren Rändern. Diese Eigenschaft übernimmt jedoch keinen Integerwert, sondern einen Point-Wert: tcMain.Padding = new Point(20,20);
Stellen Sie sich vor, Sie erzeugen ein neues Point-Objekt der Größe 20 x 20 Pixel, das auf jeder Seite des Tabseitentitels liegt. Dadurch erweitert sich die Registerkarte. Des Weiteren gibt es die SizeMode-Eigenschaft, die bestimmt, wie stark ein TabControl die Registerkarten expandieren sollte. Sie verwendet Werte aus der Aufzählung TabSizeMode: FillToRight bewirkt die Expansion bis zur Gesamtbreite des TabControl-Steuerelements, bei Fixed haben alle Registerkarten die gleiche, feste Größe und Normal legt fest, dass die Größe jede Tabseite durch die bereits genannten ItemSize- und Padding-Eigenschaften gesteuert wird. Erzeugen Sie mehr Tabseiten, als sich zugleich darstellen lassen, erscheinen auf dem TabControl Pfeile, mit deren Hilfe der Benutzer blättern kann, um alle Tabseiten zu sehen. Wenn Sie die Multiline-Eigenschaft auf true setzen, bewegen sich die Tabseiten, die normalerweise verborgen wären, in eine andere Zeile, so dass sie immer angezeigt werden – es werden so viele Zeilen erstellt wie zur Anzeige aller Tabseiten nötig sind. Um den Überblick über Ihre Steuerelemente behalten zu können, gibt es die Eigenschaften RowCount und TabCount, die, falls Multiline auf true gesetzt ist, die Anzahl der Zeilen bzw. die Gesamtzahl der Tabseiten zurückgeben. Die HotTrack-Eigenschaft schließlich veranlasst die Tabseiten, ihr Erscheinungsbild zu ändern (genauer gesagt: der Titelzeilentext ändert die Farbe), sobald die Maus darüber bewegt wird.
6.9
Die Steuerelemente TextBox und RichTextBox
Ihnen ist das TextBox-Steuerelement in diesem Buch bereits einige Male begegnet. Meistens haben Sie nur seine Text-Eigenschaft genutzt und meist brauchen Sie auch nicht mehr. Es besitzt aber noch ein paar Eigenschaften, die sich als nützlich erweisen könnten.
206
Die Steuerelemente TextBox und RichTextBox
Wir werfen einen kurzen Blick darauf, bevor ich Ihnen erzähle, was sie mit dem RichTextBox-Steuerelement zu tun haben. AcceptsReturn und AcceptsTab sind zwei Eigenschaften, die angeben, ob die (¢)- und die (ÿ_)-Tasten Zeilenumbrüche respektive Einzüge in der TextBox verursachen. Wenn der
Benutzer die Tabulatortaste drückt, geht gemäß der Voreinstellung der Fokus auf das nächste Steuerelement über und die (¢)-Taste aktiviert die Standardschaltfläche (falls es eine gibt). Indem Sie diese Eigenschaft auf true setzen, können Sie den Funktionsumfang Ihres Steuerelements erhöhen. Wenn Sie AcceptReturn gestatten, dürfen Sie damit rechnen, mehr als eine Zeile Daten zu erhalten. Tritt dies ein, dann sollten Sie Multiline auf true setzen, sonst könnte AcceptReturn nutzlos werden. Lines gibt ein Array von Zeichenfolgen zurück, das den Text aus jeder Zeile in der TextBox enthält. WordWrap gibt vor, ob der Text in die nächste Zeile weiterlaufen (also umgebrochen werden) soll, wenn die TextBox nicht groß genug ist, um ihn komplett auf einmal anzuzeigen. Ist jedoch WordWrap gleich false und Multiline gleich true, dann muss der Benutzer die (¢)-Taste drücken, um in die nächste Zeile zu gelangen. Sie können auch die Art und Weise, wie der Benutzer mit der TextBox interagiert, modifizieren. Die Eigenschaft CharacterCasing übernimmt einen Wert der CharacterCasing-Aufzählung, der angibt, wie die Großschreibung gehandhabt werden soll, wenn der Benutzer Text eingibt. Die Eigenschaft kann einen von drei Werten annehmen: Lower, welcher alle Eingaben in Kleinschreibung umwandelt; Normal, welcher Text so lässt, wie er eingegeben wird; und Upper, der jede Eingabe in Großschreibung umwandelt. Mit Hilfe von CanUndo kann der Benutzer seine letzten Aktionen rückgängig machen (für viele Benutzer ein Glücksfall!). Die Undo-Methode veranlasst die Rückgängigmachung, und ClearUndo löscht die Rückgängig-Liste, so dass der Benutzer keine Rückgängig-Aktionen mehr vornehmen kann, da die Anwendung sich nun nicht mehr »erinnern« kann, was passiert ist. Modified gibt an, ob der Text in der TextBox geändert wurde. Diese Eigenschaft wird häufig dazu verwendet, um festzustellen, ob Inhalte gespeichert werden sollten, bevor die Anwendung geschlossen wird. ReadOnly (schreibgeschützt) bedeutet, dass sich der Text nicht bearbeiten lässt. PasswordChar gibt das Zeichen an, welches zur Maskierung einer Benutzereingabe verwendet werden soll. Beim Eingeben von Kennwörtern haben Sie sicher schon oft bemerkt, dass Ihre Eingabe durch Sternchen (*) ersetzt wird. Die TextBox verfügt über eine Reihe von Methoden, die den Text behandeln. Clear löscht alle Textinhalte in der TextBox. Copy, Cut und Paste folgen alle den vertrauten WindowsOperationen Kopieren, Ausschneiden und Einfügen. AppendText hängt eine angegebene Zeichenfolge an das Ende des TextBox-Inhalts an.
207
Windows Forms mit Steuerelementen erweitern
Rich Text Das TextBox-Steuerelement eignet sich hervorragend, um Benutzereingaben zu handhaben. Sein Hauptnachteil: Es kann nur mit reinem Text oder Text ohne Formatierung umgehen. In ihrer einfachsten Form verhält sich die RichTextBox, die auch formatierten Text verarbeiten kann, wie jedes andere Steuerelement auch. Mit seinen Brüdern teilt es sich viele Mitglieder, darunter die Eigenschaften AcceptsTab, CanUndo und Multiline aus der TextBox. Doch RichTextBox bietet weit mehr Funktionen, darunter das Speichern und Laden von Dateien. Wir wollen einen Blick auf ein einfaches Beispiel für ein RichTextBox-Steuerelement werfen. Es erlaubt die Formatierung von Text. Wieder werde ich den Code aufteilen, um die Abschnitte leichter analysieren zu können. Das Listing 6.9 enthält den ersten Teil dieser Anwendung. Listing 6.9: Der Einsatz des Steuerelements RichTextBox in VB .NET 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29:
208
Imports System Imports System.Windows.Forms Imports System.Drawing Namespace TYWinForms.Day6 Public Class Listing69 : Inherits Form private rtbText as New RichTextBox private intFontSize as Integer private strFontColor as String private strFontStyle as String private private private private private private private private private private private private private private private
cmnuText as New ContextMenu cmniFont as New MenuItem cmniBold as New MenuItem cmniItalics as New MenuItem cmniNormal as New MenuItem cmniFontSize as New MenuItem cmniSize8 as New MenuItem cmniSize10 as New MenuItem cmniSize12 as New MenuItem cmniSize14 as New MenuItem cmniColor as New MenuItem cmniColorRed as New MenuItem cmniColorBlue as New MenuItem cmniColorGreen as New MenuItem cmniColorBlack as New MenuItem
public shared sub Main()
Die Steuerelemente TextBox und RichTextBox
30: 31: 32: 33:
Application.Run(new Listing69) end sub End Class End Namespace
Listing 6.9 zeigt den grundlegenden Rahmen für unsere Anwendung. Die Zeilen 8 bis 27 weisen eine Reihe von Variablen auf, von denen viele Steuerelemente vom Typ ContextMenu und MenuItem sind, die der Benutzer verwenden wird, um die Schriftart im RichTextBox-Steuerelement zu ändern. Die drei Variablen in den Zeilen 9 bis 11 sind globale Variablen, die registrieren, welche Größe, Farbe und Auszeichnung die aktuelle Schriftart hat; diese Werte werden sich später als recht nützlich erweisen. Listing 6.10 zeigt den Konstruktor dieser Beispielanwendung. Listing 6.10: Der Konstruktor für das RichTextBox-Steuerelement der Anwendung 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28:
public sub New() intFontSize = 10 strFontColor = "Schwarz" strFontStyle = "Normal" rtbText.Dock = DockStyle.Fill rtbText.ScrollBars = RichTextBoxScrollBars.Both rtbText.ContextMenu = cmnuText cmnuText.MenuItems.Add(cmniFont) cmnuText.MenuItems.Add(cmniFontSize) cmnuText.MenuItems.Add(cmniColor) cmniFont.Text = "Font" cmniBold.Text = "Fett" cmniFont.MenuItems.Add(cmniBold) cmniItalics.Text = "Kursiv" cmniFont.MenuItems.Add(cmniItalics) cmniNormal.Text = "Normal" cmniNormal.Checked = true cmniFont.MenuItems.Add(cmniNormal) AddHandler cmniBold.Click, new EventHandler(AddressOf Me.HandleFont) AddHandler cmniItalics.Click, new EventHandler(AddressOf Me.HandleFont) AddHandler cmniNormal.Click, new EventHandler(AddressOf Me.HandleFont) cmniFontSize.Text = "Grösse" cmniSize8.Text = "8" cmniFontSize.MenuItems.Add(cmniSize8)
209
Windows Forms mit Steuerelementen erweitern
29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59:
cmniSize10.Text = "10" cmniSize10.Checked = true cmniFontSize.MenuItems.Add(cmniSize10) cmniSize12.Text = "12" cmniFontSize.MenuItems.Add(cmniSize12) cmniSize14.Text = "14" cmniFontSize.MenuItems.Add(cmniSize14) AddHandler cmniSize8.Click, new EventHandler(AddressOf Me.HandleFontSize) AddHandler cmniSize10.Click, new EventHandler(AddressOf Me.HandleFontSize) AddHandler cmniSize12.Click, new EventHandler(AddressOf Me.HandleFontSize) AddHandler cmniSize14.Click, new EventHandler(AddressOf Me.HandleFontSize) cmniColor.Text = "Farbe" cmniColorRed.Text = "Rot" cmniColor.MenuItems.Add(cmniColorRed) cmniColorBlue.Text = "Blau" cmniColor.MenuItems.Add(cmniColorBlue) cmniColorGreen.Text = "Grün" cmniColor.MenuItems.Add(cmniColorGreen) cmniColorBlack.Text = "Schwarz" cmniColorBlack.Checked = true cmniColor.MenuItems.Add(cmniColorBlack) AddHandler cmniColorRed.Click, new EventHandler(AddressOf Me.HandleFontColor) AddHandler cmniColorBlue.Click, new EventHandler(AddressOf Me.HandleFontColor) AddHandler cmniColorGreen.Click, new EventHandler(AddressOf Me.HandleFontColor) AddHandler cmniColorBlack.Click, new EventHandler(AddressOf Me.HandleFontColor) Me.Text = "Listing 6.9" Me.Font = New Font("Times New Roman", intFontSize) Me.Controls.Add(rtbText) end sub
Dies ist ein Standard-Konstruktor, doch er hat eine Menge Code zu verarbeiten. Die Zeilen 2 bis 4 setzen Anfangswerte für die Schriftstile Ihrer Anwendung fest. Die Zeilen 6 bis 8 definieren Eigenschaften für das RichTextBox-Steuerelement. Dock gibt an, wie sich das Steuerelement im Verhältnis zum Formular bewegt. ScrollBars diktiert, ob und wie Bildlaufleisten für die RichTextBox angezeigt werden sollen. Diese Eigenschaft kann einen der Werte aus der RichTextBoxScrollBar-Aufzählung annehmen:
210
Die Steuerelemente TextBox und RichTextBox
왘
Both: Falls nötig, werden waagrechte und senkrechte Bildlaufleisten
erscheinen. 왘
ForcedBoth: Egal, ob benötigt oder nicht, es werden immer waagrechte und senkrechte Bildlaufleisten erscheinen.
왘
ForcedHorizontal: Es wird stets eine waagrechte Bildlaufleiste angezeigt.
왘
ForcedVertical: Es wird stets eine senkrechte Bildlaufleiste angezeigt.
왘
Horizontal: Bei Bedarf wird eine waagrechte Bildlaufleiste angezeigt.
왘
None: Es werden keine Bildlaufleisten angezeigt.
왘
Vertical: Bei Bedarf wird eine senkrechte Bildlaufleiste angezeigt.
Zeile 8 weist diesem Steuerelement ein ContextMenu zu, so dass der Benutzer darauf rechtsklicken und die Schrifteigenschaften ändern kann. Die Zeilen 10 bis 12 fügen dem ContextMenu-Steuerelement Menüelemente der höchsten Hierarchieebene hinzu. Fast der ganze Rest des Listings beschäftigt sich mit den Eigenschaften für diese MenuItems. Die Zeilen 14 bis 24 initialisieren die Menüelemente, die den Schriftstil steuern (fett, kursiv oder normal). Die Zeilen 26 bis 39 initialisieren diejenigen Elemente, die sich mit der Schriftgröße befassen, und die Zeilen 41 bis 54 betreffen die Elemente, die die Schriftfarbe steuern. Beachten Sie, dass jede Codegruppe nur einen Ereignishandler verwendet: Die Zeilen 14 bis 24 benutzen HandleFont, die Zeilen 26 bis 39 HandleFontSize und die Zeilen 41 bis 54 HandleFontColor. Die Zeilen 56 bis 59 initialisieren schließlich das Formular, wobei sie die zu benutzende Standardschrift festlegen. Listing 6.11 zeigt die Ereignishandler und diverse Funktionen der Anwendung. Listing 6.11: Die Behandlung der RichTextBox-Ereignisse 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11:
public sub HandleFont(Sender as Object, e as EventArgs) strFontStyle = CType(Sender, MenuItem).Text rtbText.SelectionFont = new Font("Times New Roman", intFontSize, ConvertToFontStyle(strFontStyle)) cmniBold.Checked = false cmniItalics.Checked = false cmniNormal.Checked = false CType(Sender, MenuItem).Checked = true end sub
211
Windows Forms mit Steuerelementen erweitern
12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45:
public sub HandleFontSize(Sender as Object, e as EventArgs) intFontSize = Convert.ToInt32(CType(Sender, MenuItem).Text) rtbText.SelectionFont = new Font("Times New Roman", intFontSize, ConvertToFontStyle(strFontStyle)) cmniSize8.Checked = false cmniSize10.Checked = false cmniSize12.Checked = false cmniSize14.Checked = false CType(Sender, MenuItem).Checked = true end sub public sub HandleFontColor(Sender as Object, e as EventArgs) strFontColor = CType(Sender, MenuItem).Text rtbText.SelectionColor = Color.FromName(strFontColor) cmniColorRed.Checked = false cmniColorBlue.Checked = false cmniColorGreen.Checked = false cmniColorBlack.Checked = false CType(Sender, MenuItem).Checked = true end sub private function ConvertToFontStyle(strStyle) as FontStyle select strStyle case "Fett" return FontStyle.Bold case "Kursiv" return FontStyle.Italic case "Normal" return FontStyle.Regular end select end function
Beachten Sie bitte zunächst, dass keine dieser Methoden eines der Ereignisse in RichTextBox direkt verarbeitet. Vielmehr behandeln wir die Ereignisse der MenuItem-Steuerelemente, die die Eigenschaften der RichTextBox entsprechend anpassen. Lassen Sie uns als Nächstes hinunter zur Funktion ConvertToFontStyle springen, die in Zeile 36 beginnt. Wie Sie aus ihrer Deklaration schließen können, gibt diese Funktion einen Wert der FontStyle-Aufzählung zurück. Wenn Sie nacheinander jedes Ereignis behandeln, werden Sie bemerken, dass es nötig wird, das vom Benutzer ausgewählte Menu-
212
Die Steuerelemente TextBox und RichTextBox
Item in einen passenden Typ umzuwandeln, den die RichTextBox verwenden kann. So kön-
nen Sie etwa mit folgendem Befehl keine Schrift erzeugen: new Font("Times New Roman", 8, "Fett")
Statt dessen müssen Sie den Wert der FontStyle-Aufzählung verwenden: new Font("Times New Roman", 8, FontStyle.Fett)
Die Funktion ConvertToFontStyle wandelt den Text »Fett« in einen FontStyle.Bold-Wert um, den Sie an anderer Stelle in Ihrem Code benutzen können. Wie genau, werden Sie gleich sehen. Wenn Sie zu Zeile 1 und der HandleFont-Methode zurückgehen, denken Sie bitte daran, dass diese Methode das Click-Ereignis für die Menüelemente cmniBild, cmniItalics und cmniNormal behandelt, die die Schriftart jeweils fett oder kursiv auszeichnen bzw. auf normal zurücksetzen. In Zeile 2 holen Sie den Text-Wert vom gerade ausgewählten MenuItem in die strFontStyle-Variable (Sie müssen die Sender-Variable in einen passenden Typ umwandeln – in diesem Fall MenuItem); dieser Wert wird »Fett« sein, »Kursiv« oder »Normal«, wie in den Zeilen 15, 17 und 19 des Listings 6.10 gezeigt wird. In Zeile 3 verwenden Sie die Eigenschaft SelectionFont der RichTextBox. Sie holt und setzt Informationen über die gerade in der RichTextBox verwendete Schriftart, insbesondere den gerade ausgewählten (oder markierten) Text. Ist momentan kein Text ausgewählt, betrifft diese Eigenschaft jeden neuen Text, den der Benutzer eingibt. Zeile 3 weist SelectionFont ein neues Font-Objekt zu, wobei die in den Variablen intFontSize und strFontStyle gesetzten Werte verwendet werden. Nun verstehen Sie, warum diese globalen Variablen wichtig sind – ohne sie würde es viel mehr Mühe bereiten, neue Schriftstile festzulegen. Ohne globale Variablen würde es sicherlich mehr Arbeit bedeuten, doch sie wäre nicht schwieriger. Wenn Sie diese globalen Variablen nicht verwenden wollen, dann ändern Sie die Zeile 3 wie folgt: rtbText.SelectionFont = new Font("Times New Roman"), rtbText.SelectionFont.Size, ConvertToFontStyle(Ctype(Sender, MenuItem).Text))
Mit anderen Worten: Die globalen Variablen spielen Platzhalter für Werte der SelectionFont-Eigenschaft und der Sender-Variablen. Lassen Sie uns kurz zur ConvertToFontStyle-Funktion zurückkehren. Bekanntlich kann man die Zeichenfolge, die vom ausgewählten Menüelement abgerufen wird, nicht direkt verwenden. Daher gibt diese Methode den richtigen Wert der FontStyle-Aufzählung zurück, um die Schrift richtig festzulegen. An Tag 4 behandelten wir Menüelemente. Wenn man zulässt, dass Menüelemente mit Häkchen versehen werden, dann muss man dafür sorgen, dass alle momentan nicht rele-
213
Windows Forms mit Steuerelementen erweitern
vanten Menüelemente kein Häkchen aufweisen. Die Zeilen 5 bis 9 entfernen alle Häkchen von den Schriftstil-Menüelementen; dann wird das passende Menüelement mit Häkchen versehen, je nachdem, was die Sender-Variable angibt. Die Methoden HandleFontSize und HandleFontColor tun praktisch das Gleiche wie HandleFont. Beide holen den Text des ausgewählten Menüelements (die Zeilen 13 respektive 25). HandleFontSize weist der SelectionFont-Eigenschaft unter Zuhilfenahme der globalen Variablen eine neue Schrift zu und versieht die Menüelemente dann mit Häkchen oder entfernt diese, je nachdem. HandleFontColor verwendet eine etwas andere Eigenschaft, nämlich SelectionColor. Diese Eigenschaft holt oder setzt die für den gerade ausgewählten Text verwendete Farbe (ist gerade keiner ausgewählt, dann für jeden neuen Text). In Zeile 26 verwenden Sie die FromName-Methode des Color-Objekts, um die Farbe zuzuweisen. In den Zeilen 28 bis 33 werden die Häkchen gesetzt bzw. entfernt. Diese Anwendung enthält eine Menge Code; tatsächlich ist sie das umfangreichste Codebeispiel, das Sie bis jetzt gesehen haben. Doch das meiste davon ist recht einfach, denn es besteht vor allem aus dem Initialisierungscode des Konstruktors. Kombinieren Sie diese drei Listings, indem Sie Listing 6.10 und 6.11 in Zeile 28 des Listings 6.9 einfügen. Abbildung 6.11 zeigt ein typisches Ergebnis aus dieser Anwendung.
Abbildung 6.11: Die voll funktionsfähige RichTextBox-Anwendung bietet dem Benutzer zahlreiche Wahlmöglichkeiten an.
Die Hauptarbeit bei der Verwendung des RichTextBox-Steuerelements besteht also vor allem in der Bereitstellung von Benutzerschnittstellen (wie Menüelementen), um Schrifteigenschaften bearbeiten zu können. Die zwei Haupteigenschaften SelectionFont und SelectionColor betreffen den gerade ausgewählten Text. Die RichTextBox besitzt viele solcher Eigenschaften, darunter SelectionAlignment, die anzeigt, wie der momentan markierte Text auszurichten ist, SelectionBullet, die festlegt, ob der Text eine Liste mit Aufzählpunkten sein soll, und schließlich SelectionIndent, die festlegt, wie groß Einrückungen sein sollen. (Um etwas über die anderen Eigenschaften zu erfahren, schlagen Sie bitte in Anhang B nach.)
214
Das Steuerelement Timer
Bevor wir zum nächsten Steuerelement übergehen, sind drei RichTextBox-Mitglieder vorzustellen, die jeder Entwickler kennen sollte. Das erste, DetectUrls, ist interessant, da seine Funktion zunehmende Bedeutung in heutigen Anwendungen erlangt. Wird diese Eigenschaft auf true gesetzt, formatiert die RichTextBox jeden Text, der wie eine Webadresse (URL) aussieht, auf solche Weise, dass er anklickbar ist – genau wie eine normale Verknüpfung in einem Webbrowser. Der fragliche Text ist blau und unterstrichen (je nach Ihren Internet-Einstellungen). Um Klicks auf solche Links handhaben zu können, müssen Sie dem LinkClicked-Ereignis der RichTextBox einen Delegaten zuweisen und einen Ereignishandler hinzufügen, so etwa: public Sub Link_Clicked(Sender As Object, e As LinkClickedEventArgs) System.Diagnostics.Process.Start(e.LinkText) end Sub
Die Start-Methode in diesem Codestück ruft den Standard-Browser des Benutzers mit dem Link, den er angeklickt hat, auf (eigentlich ruft sie die Anwendung auf, die dafür konfiguriert ist, Internet-Links zu verarbeiten, was aber meist ein Browser ist). Das zweite kennen zu lernende RichTextBox-Mitglied ist die Find-Methode. Sie durchsucht die RichTextBox nach einer angegebenen Zeichenfolge – eine sehr nützliche Funktion für jede textverarbeitende Anwendung. Diese Methode kann etliche verschiedene Parameter übernehmen, doch die gebräuchlichsten sind eine Zeichenfolge sowie eine Zeichenfolge mit einem Index für das erste Zeichen, bei dem die Suche beginnen soll. Beim letzten Mitglied handelt es sich eigentlich um zwei, die eine ähnliche Funktion haben. Rtf und SelectedRtf liefern den gesamten bzw. nur den markierten Text in der RichTextBox, wobei sie alle Formatierungen übernehmen. Die Copy-Methode, die auch in der TextBox verfügbar ist, kopiert Text, doch verlieren Sie dabei alle Formatierungen. Verwenden Sie also Rtf oder SelectedRtf, um die Formatierung beizubehalten, wenn Sie den Text an eine andere Anwendung (wie zum Beispiel Microsoft Word) übergeben wollen.
6.10 Das Steuerelement Timer Das Timer-Steuerelement ist eines der nützlichsten Steuerelemente überhaupt. Einfach ausgedrückt, können Sie damit ein Ereignis nach einem festgelegten Intervall auslösen. Bis jetzt hatten Sie keine Möglichkeit, in einer Anwendung Zeit oder Dauer festzuhalten. Denken Sie einmal an all die Einsatzmöglichkeiten von Timer: Sie können eine Anwendung erstellen, die den Benutzer alle fünf Minuten daran erinnert, sein Dokument zu speichern; Sie können Echtzeituhren und -zähler bereitstellen und eine Leistungsüberwachung einrichten. Praktisch jede interaktive Aufgabe, die Sie automatisieren wollen, erfordert das TimerSteuerelement.
215
Windows Forms mit Steuerelementen erweitern
Da er kaum etwas anderes tut, als die Zeit zu stoppen, verfügt der Timer über sehr wenige Mitglieder. Er hat nur zwei Eigenschaften: Enabled zeigt an, ob der Timer aktiv sein soll, und Interval legt die Dauer in Millisekunden fest, nach der der Timer »ticken« soll. Jeder »Tick« im Timer erzeugt ein Ereignis, das treffend einfach Tick genannt wird. Damit können Sie angepasste automatische Routinen ausführen lassen. Schließlich gibt es noch zwei interessante Methoden, nämlich Start und Stop, die einfach den Timer starten bzw. anhalten. Eine der besten Anwendungsmöglichkeiten von Timer besteht in einer Weckuhr. Listing 6.12 zeigt eine solche Anwendung. Listing 6.12: Eine Weckuhr 1: Imports System 2: Imports System.Windows.Forms 3: Imports System.Drawing 4: 5: namespace TYWinForms.Day6 6: 7: public class Listing612 : Inherits Form 8: private tmrClock as New Timer 9: private tmrAlarm as New Timer 10: private lblClock as New Label 11: private intSnoozeTime as Integer 12: private intSnoozeCounter as Integer 13: 14: public sub New() 15: intSnoozeTime = 10000 '10 Sekunden 16: 17: tmrClock.Interval = 1000 18: tmrClock.Enabled = true 19: AddHandler tmrClock.Tick, new EventHandler(AddressOf Me.UpdateClock) 20: 21: tmrAlarm.Interval = 1000 22: tmrAlarm.Enabled = true 23: AddHandler tmrAlarm.Tick, new EventHandler(AddressOf Me.CheckAlarm) 24: 25: lblClock.Width = 300 26: lblClock.Height = 150 27: lblClock.Location = new Point(0,100) 28: lblClock.Text = DateTime.Now.ToString 29: 30: Me.Text = "Listing 6.12" 31: Me.Font = new Font("Arial", 20) 32: Me.Controls.Add(lblClock)
216
Das Steuerelement Timer
33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52:
end sub public sub UpdateClock(Sender as Object, e as EventArgs) lblClock.Text = DateTime.Now.ToString end sub public sub CheckAlarm(Sender as Object, e as EventArgs) intSnoozeCounter += tmrAlarm.Interval if intSnoozeCounter = intSnoozeTime then intSnoozeCounter = 0 MessageBox.Show("Aufwachen, Schlafmütze!!!") end if end sub public shared sub Main() Application.Run(new Listing612) end sub end class end namespace
In dieser Anwendung erzeugen Sie zwei Timer-Steuerelemente: eines, um die dem Benutzer sichtbare Uhr zu aktualisieren, und eines, um den »Weckruf« zu steuern. In den Zeilen 8 bis 12 werden diese beiden Timer neben ein paar weiteren Variablen deklariert. In Zeile 15 stellen Sie im Konstruktor die Schlummerzeit ein oder die Zeit, bevor der Alarm losgeht – in unserem Fall nach 10 Sekunden (= 10.000 Millisekunden). In den Zeilen 17 bis 19 stellen Sie das Intervall des Timers auf eine Sekunde (oder 1000 Millisekunden) ein, aktivieren bzw. starten den Timer und übergeben ihm einen Ereignishandler. Die Zeilen 21 bis 23 erledigen das Gleiche für den Weckruf-Timer. Ein paar Eigenschaften für ein Label-Steuerelement, das die Uhr anzeigt, werden in den Zeilen 25 bis 28 festgelegt, und die Zeilen 30 bis 32 bestimmen ein paar allgemeine Eigenschaften für das Formular. Beachten Sie, dass Sie die DateTime.Now-Methode verwenden, um die aktuelle Zeit zurückzugeben, wie sie von der Uhr des Benutzer-Computers definiert wird. Die ToString-Methode wandelt das DateTime-Objekt in Text um, der im Label-Steuerelement angezeigt wird. Die UpdateClock-Methode in Zeile 35 wird alle 1000 Millisekunden durch den Timer tmrClock ausgeführt. Sie ruft einfach fortlaufend DateTime.Now auf, um die sichtbare Uhr zu aktualisieren. Auch CheckAlarm wird alle 1000 Millisekunden ausgeführt und bestimmt, ob das vorgegebene Schlummerintervall bereits verstrichen ist. In Zeile 40 erhöht
217
Windows Forms mit Steuerelementen erweitern
diese Methode die intSnoozeCounter-Variable, die in Zeile 42 mit der intSnoozeTime-Variablen verglichen wird. Sind beide gleich – sind also 10 Sekunden verstrichen – wird eine Meldung mit einer freundlichen Weckrufbotschaft angezeigt. Die Zählervariable wird dann auf 0 zurückgesetzt, um den Weckrufvorgang von neuem zu starten. Ansonsten passiert nichts, und der Timer tickt unverdrossen weiter. Abbildung 6.12 zeigt das Ergebnis nach zehn Sekunden.
Abbildung 6.12: Ihre einfache Weckuhr besitzt einen stillen Alarmruf.
6.11 Das Steuerelement TreeView Das Steuerelement TreeView wird für die Anzeige hierarchischer Listen von Informationen verwendet. Die gebräuchlichste Einsatzweise besteht in Oberflächen vom Typ des Windows Explorers: Sie können damit durch eine Verzeichnisstruktur blättern, wie in Abbildung 6.13 zu sehen. Eine Verzeichnisstruktur ist jedoch nicht das Einzige, das das TreeView-Steuerelement darstellen kann. Jede beliebige hierarchisch aufgebaute Informationsstruktur – Kunden- und Bestelldaten, Stammbäume, Speisekarten usw. – lässt sich mit TreeView handhaben. Jedes Element – oder Strukturknoten (node) – in der TreeView-Liste wird durch ein TreeNode-Objekt dargestellt. Natürlich kann jeder TreeNode unter sich weitere TreeNodes haben, so dass das Steuerelement seine hierarchische Charakteristik erhält. Der erste Knoten wird als Wurzel- oder Stammknoten (root node) bezeichnet. Da Sie jeden Knoten in der TreeView einzeln anlegen müssen, könnte das Anlegen dieses Steuerelements etwas mühselig sein, doch es ist zum Glück nicht schwierig. Das folgende Codestück erstellt z.B. neue Knoten einfach durch Einsatz von Programmschleifen:
218
Das Steuerelement TreeView
Abbildung 6.13: Die Benutzeroberfläche des Windows Explorers verwendet eine Strukturansicht. dim myTreeView as new TreeView For i = 0 to 20 MyTreeView.Nodes.Add(New TreeNode("Node " &&i.ToString)) For j = 0 to 20 MyTreeView.Nodes(i).Nodes.Add(New TreeNode("SubNode " &&j.ToString)) For k = 0 to 20 MyTreeView.Nodes(i).Nodes(j).Nodes.Add(New TreeNode("SubSubNode " &&k.ToString)) Next k Next j Next i
In diesem Codestück werden Schleifen durch drei verschiedene Variablen zwischen 0 und 20 geführt. Das Ergebnis ist ein dreischichtiges TreeView-Steuerelement, wobei jede Schicht über 21 Knoten verfügt. Neue Knoten lassen sich einfach mit der Nodes.AddMethode hinzufügen, wobei ein neues TreeNode-Objekt mit einer festgelegten Beschriftung übergeben wird. Auf diese Beschriftung kann man auch über die Text-Eigenschaft jedes Knotens zugreifen. Das TreeView-Steuerelement lässt sich weitgehend anpassen; jeder Aspekt der Benutzeroberfläche lässt sich steuern. Tabelle 6.3 zeigt die gebräuchlichsten Eigenschaften von TreeView.
219
Windows Forms mit Steuerelementen erweitern
Eigenschaft
Beschreibung
CheckBoxes
Gibt an, ob neben jedem Knoten Kontrollkästchen angezeigt werden sollen. Dadurch kann der Benutzer bestimmte Knoten zur Bearbeitung auswählen. Sie verwenden die Checked-Eigenschaft des TreeNode-Objekts, um zu bestimmen, ob ein Objekt mit Häkchen versehen (checked) ist.
FullRowSelect
Gibt an, ob die Markierung, die bei der Auswahl eines Knotens erscheint, die gesamte Breite des Steuerelements einnehmen oder sich auf die Beschriftung des Knotens beschränken soll.
HotTracking
Gibt an, ob sich das Aussehen des Knotens ändert, wenn sich der Mauszeiger darüber befindet (z.B. der Text sich blau färbt)
ImageList
Ein ImageList-Steuerelement, das sich dazu einsetzen lässt, Grafiken anstelle der Knotensymbole anzuzeigen. Sie verwenden dies mit der ImageIndex-Eigenschaft genau wie bei anderen Steuerelementen.
Indent
Die Anzahl an Pixeln, um die jeder untergeordnete Knoten gegenüber seinem übergeordneten Knoten eingerückt werden soll.
LabelEdit
Gibt an, ob sich die Beschriftung eines Knotens bearbeiten lässt.
Nodes
Gibt eine Auflistung von Strukturknoten (TreeNode) zurück.
PathSeparator
Das für die Abtrennung von Knoten verwendete Zeichen (mehr dazu gleich).
ShowLines
Gibt an, ob in der Strukturansicht Verbindungslinien zwischen den Knoten gezeichnet werden.
ShowPlusMinus
Gibt an, ob neben Knoten mit untergeordneten Knoten Plus- und Minuszeichen angezeigt werden sollen.
ShowRootLines
Gibt an, ob zwischen Stammknoten der Strukturansicht Verbindungslinien gezeichnet werden sollen.
Scrollable
Gibt an, ob nach Bedarf Bildlaufleisten angezeigt werden sollen.
VisibleCount
Die Anzahl der gleichzeitig sichtbaren Knoten.
TopNode
Gibt das oberste sichtbare TreeNode-Objekt zurück.
Tabelle 6.3: Die gebräuchlichsten Eigenschaften des TreeView-Steuerelements
Das TreeNode-Objekt weist ebenfalls einige nützliche Eigenschaften auf. FullPath kombiniert die Bezeichnungstexte aller Knoten, die anzusteuern sind, um den aktuellen Knoten zu erreichen. Die Angabe ist durch das PathSeparator-Zeichen (Standardwert: \) aufgeteilt. Beispielsweise sähe der FullPath, also die vollständige Pfadangabe, zum 14. Knoten,
220
Zusätzliche nützliche Steuerelemente
der zum Knoten gehört, der als 2. Knoten dem 3. Wurzelknoten untergeordnet ist, mit Hilfe des PathSeparator-Zeichens wie folgt aus: "Node2\SubNode1\SubSubNode13\"
Die Eigenschaften IsEditing, IsExpanded, IsSelected und IsVisible geben true- oder false-Werte zurück, um anzugeben, ob der aktuelle TreeNode gerade bearbeitet wird oder sichtbar, erweitert oder ausgewählt ist. Um Strukturknoten anzusteuern, stehen die Eigenschaften NextNode und NextVisibleNode sowie PrevNode und PrevVisibleNode zur Verfügung. Diese Eigenschaften geben die spezifizierten Strukturknoten zurück. Zwei nützliche TreeView-Methoden sind CollapseAll und ExpandAll, die entweder alle erweiterten Knoten reduzieren oder umgekehrt alle reduzierten Knoten erweitern. In analoger Weise verfügt TreeNode über Collapse, Expand, ExpandAll und Toggle. Letztere schaltet zwischen den Zuständen erweitert/reduziert hin und her. Das TreeView-Steuerelement weist eine Reihe von Ereignissen auf, die sowohl vor als auch nach bestimmten Aktionen ausgelöst werden. Diese Ereignisse verwenden die Präfixe Before (vor) und After (nach), so etwa in BeforeCheck, BeforeCollapse, BeforeExpand, BeforeLabelEdit und BeforeSelect (zusammen mit den entsprechenden After-Methoden).
6.12 Zusätzliche nützliche Steuerelemente In der heutigen Lektion haben wir gerade mal die Oberfläche der Windows Forms-Steuerelemente angekratzt. Wir haben die gebräuchlichsten Steuerelemente erforscht, doch es gibt insgesamt 42 Steuerelemente. Hier eine kleine Kostprobe: 쐽
CheckListBox: Ähnelt der ListBox, platziert aber neben jedem Listenelement ein kleines Kästchen. Da es sich um eine Kombination aus CheckBox und ListBox handelt,
können Sie sich die Eigenschaften in diesem Steuerelement vorstellen. 쐽
DataGrid: Sehr nützlich beim Anzeigen von Informationen aus gespeicherten Datenquellen wie etwa Datenbanken. (Mehr dazu an Tag 9.)
쐽
ProgressBar: Ähnlich den »Leisten-« Steuerelementen, die Sie an Tag 4 kennen gelernt haben. Die Fortschrittsleiste wird von links nach rechts angefüllt, um anzuzeigen, wie weit die Vollendung einer Aufgabe vorangeschritten ist.
쐽
ToolTip: Stellt eine QuickInfo dar, die erscheint, wenn der Mauszeiger über einem
Steuerelement schwebt. Windows ist voll von solchen QuickInfos, so dass dieses Steuerelement sich als recht hilfreich erweist.
221
Windows Forms mit Steuerelementen erweitern
6.13 Das Layout steuern Es ist an der Zeit zu lernen, wie man all diese Steuerelemente ordentlich auf einem Formular anordnet, denn sonst würde man ein unverständliches Durcheinander erhalten. Sie haben bereits eine Organisationsmethode kennen gelernt: das Hinzufügen von Steuerelementen zu anderen, übergeordneten Steuerelementen, wie etwa Panel- oder TabControlSteuerelementen. Mit diesen beiden lassen sich Steuerelemente zu logischen Gruppen zusammenfassen, die sowohl verwaltbar (als auch leicht darstellbar) sind. Es gibt noch andere Möglichkeiten zur Organisation von Steuerelementen. Eine davon besteht in der Dock-Eigenschaft der Control-Klasse. Mit Dock lassen sich die Position sowie die Art und Weise angeben, in der ein fragliches Steuerelement an sein Containersteuerelement angedockt ist. Indem man Werte aus der DockStyle-Aufzählung verwendet, kann man Steuerelemente dazu bringen, am oberen oder unteren Rand eines Formulars oder eines Panel-Steuerelements (wie etwa Symbolleisten oder Bildlaufleisten) zu kleben oder den Container vollständig auszufüllen. Folgende sind die DockStyle-Werte: 쐽
Bottom: Dockt das Steuerelement am unteren Rand des Containersteuerelements an.
쐽
Fill: Alle Ränder des Steuerelements werden an die entsprechenden gegenüberliegenden Seiten des Containers angedockt, wodurch das Steuerelement den Container vollständig ausfüllt, selbst wenn dessen Größe sich ändert.
쐽
Left: Dockt das Steuerelement am linken Rand des Containersteuerelements an.
쐽
None: Standardwert; das Steuerelement wird nirgendwo angedockt.
쐽
Right: Dockt das Steuerelement am rechten Rand des Containersteuerelements an.
쐽
Top: Dockt das Steuerelement am oberen Rand des Containersteuerelements an.
Eine weitere das Layout beeinflussende Eigenschaft ist Anchor. Sie ist ebenfalls in jedem Steuerelement vorhanden und ähnelt Dock insofern, als sie festlegt, welche Ränder eines Steuerelements mit dem übergeordneten Formular verknüpft sein sollen, doch im Unterschied zu Dock werden die Steuerelemente dadurch nicht an den Formularrändern angedockt. Vielmehr bleibt die angegebene Seite des Steuerelements an ihrer Position relativ zum Rand des Formulars, unabhängig von seiner Position auf dem Formular oder von der Größenänderung des Formulars. Anchor nimmt einen der Werte der AnchorStyle-Aufzählung an: Bottom, Left, None, Right oder Top. Damit ein Steuerelement seine Größe relativ zu der des Formulars ändert, setzen Sie die Verankerung auf gegenüberliegende Seiten des Formulars. Dies erfolgt in VB .NET mit dem Or-Operator und in C# mit dem Zeichen | – etwa so: textBox1.Anchor = AnchorStyles.Top Or AnchorStyles.Bottom textBox1.Anchor = AnchorStyles.Top | AnchorStyles.Bottom;
222
Das Layout steuern
Listing 6.13 zeigt ein gutes Beispiel für die Manipulation der Eigenschaften Dock und Anchor. Listing 6.13: Das dynamische Ändern eines Steuerelementlayouts (Visual Basic .NET) 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41:
Imports System Imports System.Windows.Forms Imports System.Drawing Namespace TYWinForms.Day6 Public Class Listing613 : Inherits Form Private WithEvents pnlButton As New Panel Private WithEvents pnlControls As New Panel Private WithEvents gboxAnchor As New GroupBox Private WithEvents gboxDock As New GroupBox Private WithEvents btnDemo As New Button Private WithEvents rbNone As New RadioButton Private WithEvents rbTop As New RadioButton Private WithEvents rbLeft As New RadioButton Private WithEvents rbBottom As New RadioButton Private WithEvents rbRight As New RadioButton Private WithEvents rbFill As New RadioButton Private WithEvents chkTop As New CheckBox Private WithEvents chkLeft As New CheckBox Private WithEvents chkBottom As New CheckBox Private WithEvents chkRight As New CheckBox Public Sub New() rbRight.Location = New Point(8,120) rbRight.Size = New Size(72,24) rbRight.Text = "Rechts" rbNone.Location = New Point(8,24) rbNone.Size = New Size(72,24) rbNone.Text = "Kein" rbNone.Checked = True rbBottom.Location = New Point(8,96) rbBottom.Size = New Size(72,24) rbBottom.Text = "Unten" rbTop.Location = New Point(8,48) rbTop.Size = New Size(72,24) rbTop.Text = "Oben"
223
Windows Forms mit Steuerelementen erweitern
42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80: 81: 82: 83: 84: 85: 86: 87:
224
rbLeft.Location = New Point(8,72) rbLeft.Size = New Size(72,24) rbLeft.Text = "Links" rbFill.Location = New Point(8,144) rbFill.Size = New Size(72,24) rbFill.Text = "Füllen" gboxAnchor.Location = New Point(16,16) gboxAnchor.Size = New Size(88,128) gboxAnchor.Text = "Verankern" gboxDock.Location = New Point(16,152) gboxDock.Size = New Size(88,176) gboxDock.Text = "Andocken" btnDemo.Size = New Size(120,24) btnDemo.Anchor = AnchorStyles.None btnDemo.Location = New Point(64,72) btnDemo.Text = "Spiel mit mir!" chkBottom.Location = New Point(8,72) chkBottom.Size = New Size(72,24) chkBottom.Text = "Unten" chkLeft.Location = New Point(8,48) chkLeft.Size = New Size(72,24) chkLeft.Text = "Links" chkTop.Location = New Point(8,24) chkTop.Size = New Size(72,24) chkTop.Text = "Oben" chkRight.Location = New Point(8,96) chkRight.Size = New Size(72,24) chkRight.Text = "Rechts" pnlButton.BorderStyle = FormBorderStyle.Fixed3D pnlButton.Dock = DockStyle.Fill pnlButton.Size = New Size(448,400) pnlButton.Text = "ButtonPanel" pnlButton.Controls.Add(btnDemo) pnlControls.BorderStyle = FormBorderStyle.Fixed3D pnlControls.Dock = DockStyle.Right pnlControls.Location = New Point(328,0)
Das Layout steuern
88: pnlControls.Size = New Size(120,400) 89: pnlControls.Text = "ControlsPanel" 90: pnlControls.Controls.Add(gboxAnchor) 91: pnlControls.Controls.Add(gboxDock) 92: 93: gboxAnchor.Controls.Add(chkRight) 94: gboxAnchor.Controls.Add(chkBottom) 95: gboxAnchor.Controls.Add(chkLeft) 96: gboxAnchor.Controls.Add(chkTop) 97: gboxDock.Controls.Add(rbBottom) 98: gboxDock.Controls.Add(rbLeft) 99: gboxDock.Controls.Add(rbNone) 100: gboxDock.Controls.Add(rbRight) 101: gboxDock.Controls.Add(rbFill) 102: gboxDock.Controls.Add(rbTop) 103: 104: Me.Text = "Listing 6.13" 105: Me.Size = New Size(800,600) 106: Me.Controls.Add(pnlButton) 107: Me.Controls.Add(pnlControls) 108: End Sub 109: 110: Private Sub AnchorClicked(Sender As Object,e As EventArgs) Handles chkBottom.Click,chkLeft.Click,chkRight.Click,chkTop.Click 111: If chkTop.Checked Then 112: btnDemo.Anchor = btnDemo.Anchor Or AnchorStyles.Top 113: End If 114: 115: If chkLeft.Checked Then 116: btnDemo.Anchor = btnDemo.Anchor Or AnchorStyles.Left 117: End If 118: 119: If chkBottom.Checked Then 120: btnDemo.Anchor = btnDemo.Anchor Or AnchorStyles.Bottom 121: End If 122: 123: If chkRight.Checked Then 124: btnDemo.Anchor = btnDemo.Anchor Or AnchorStyles.Right 125: End If 126: End Sub 127: 128: Private Sub DockClicked(Sender As Object,e As EventArgs) Handles rbBottom.Click,rbFill.Click,rbLeft. Click,rbRight.Click,rbTop.Click,rbNone.Click 129: dim rbSet as RadioButton = CType(sender,RadioButton) 130:
225
Windows Forms mit Steuerelementen erweitern
131: 132: 133: 134: 135: 136: 137: 138: 139: 140: 141: 142: 143: 144: 145: 146: 147: 148: 149: 150:
If rbSet Is rbNone Then btnDemo.Dock = DockStyle.None ElseIf rbSet Is rbTop Then btnDemo.Dock = DockStyle.Top ElseIf rbSet Is rbLeft Then btnDemo.Dock = DockStyle.Left ElseIf rbSet Is rbBottom Then btnDemo.Dock = DockStyle.Bottom ElseIf rbSet Is rbRight Then btnDemo.Dock = DockStyle.Right Else btnDemo.Dock = DockStyle.Fill End If End Sub public shared sub Main() Application.Run(New Listing613) end sub End Class End Namespace
In den Zeilen 8 bis 22 richten Sie einfach einige Steuerelemente ein, mit denen der Benutzer die Anchor- und Dock-Eigenschaften ändern kann; Sie benötigen ein Optionsfeld oder ein Kontrollkästchen für jeden der Werte in den DockStyle- und AnchorStyle-Aufzählungen (verwenden Sie Optionsfelder für DockStyle, denn es lässt sich nur jeweils ein Wert zuweisen; Kontrollkästchen werden für AnchorStyle benutzt, denn man kann ja mehrere Verankerungen zugleich haben). Beachten Sie den Gebrauch des Schlüsselwortes WithEvents: Dies teilt der CLR mit, dass jedes einzelne der Steuerelemente Ereignisse auslösen wird (in C# ist das Einbeziehen solcher Schlüsselworte nicht nötig). In den Zeilen 25 bis 107, also dem Großteil des Codes, initialisieren und platzieren wir Steuerelemente im Formular. In diesem Listing findet sich ein neues Steuerelement namens GroupBox (Gruppenfeld). Es ähnelt dem Panel-Steuerelement, weil es andere Steuerelemente enthält, doch es zeichnet einen Kasten um seine untergeordneten Steuerelemente, so dass der Benutzer weiß, dass diese Steuerelemente zusammengehören. In den Zeilen 93 bis 102 werden die einzelnen Steuerelemente diesen Gruppenfeldern hinzugefügt, welche wiederum (in den Zeilen 90 und 91) Panel-Steuerelementen hinzugefügt werden. Die Panel-Steuerelemente finden in den Zeilen 106 und 107 endlich Aufnahme in das Formular. Es gilt nur noch zwei Methoden zu untersuchen: AnchorClicked in Zeile 110 und DockClicked in Zeile 128. Sie verwenden das Schlüsselwort Handles, um der CLR mitzuteilen, auf welche Ereignisse diese Methoden angewendet werden sollen (mehr zur Ereignisbehand-
226
Das Layout steuern
lung in Windows Forms finden Sie an Tag 5). In unserem Fall handhabt die AnchorClicked-Methode alle Click-Ereignisse der Kontrollkästchen, und DockClicked tut das Gleiche für die Optionsfelder. Wir wollen zuerst AnchorClicked betrachten. Da sich Anchor-Eigenschaften kumulativ auf ein Steuerelement anwenden lassen, müssen Sie jedes einzelne Anker-Kontrollkästchen untersuchen. Ist das Kontrollkästchen für AnchorStyles.Top angekreuzt, müssen Sie den AnchorStyles.Top-Wert anwenden, und so weiter für alle weiteren Werte. Dies lässt sich leicht bewerkstelligen, indem man mit Hilfe des Or-Operators die Werte in if-Statements der Anchor-Eigenschaft einer Schaltfläche hinzufügt. Als Folge dieser Vorgehensweise wird jeder AnchorStyles-Wert nach Bedarf in der Reihenfolge der if-Statements ausgewertet und kumulativ angewendet. Mit DockClicked verhält es sich ähnlich. Doch da sich DockStyle-Werte nur einzeln anwenden lassen, müssen Sie die Stile nicht zusammen hinzufügen. Mit Hilfe von ifStatements bestimmen Sie einfach, welches Optionsfeld angeklickt wurde, und wenden den passenden DockStyle-Wert an (beachten Sie den Gebrauch des Is-Schlüsselworts: Denken Sie beim Vergleichen von Objekten daran, diesen Operator anstelle des Gleichheitszeichens (=) zu verwenden). Probieren Sie die Steuerelemente der Anwendung, die in Abbildung 6.14 zu sehen ist, aus und beachten Sie, wie die Schaltfläche dadurch beeinflusst wird. Obwohl die Eigenschaften TabStop und TabIndex das Layout von Steuerelementen nicht direkt betreffen, so gestatten sie doch das Navigieren zwischen den einzelnen Steuerelementen, so dass sie an dieser geeigneten Stelle vorgestellt werden sollen. Als WindowsBenutzer wissen Sie, dass Sie die (ÿ_)-Tasten dazu benutzen können, von einem Steuerelement zum nächsten zu gelangen. Dadurch können Sie sich ohne Maus durch eine Anwendung manövrieren. Die TabIndex-Eigenschaft gibt an, in welcher Reihenfolge bestimmte Steuerelemente angesteuert werden, wenn die (ÿ_)-Taste gedrückt wird. Ein Steuerelement mit dem TabIndex von 0 ist das erste zu markierende Steuerelement, sobald eine (ÿ_)-Taste gedrückt wird; TabIndex gleich 1 ist das nächste und so weiter. Jedes Steuerelement im Formular lässt sich auf diese Weise ansteuern. Wenn Sie die TabIndex-Werte nicht ausdrücklich zuweisen, erledigt dies die CLR für Sie, allerdings gemäß der Reihenfolge, in der das jeweilige Steuerelement dem Formular hinzugefügt wurde (und falls Sie aus Versehen eine TabIndex-Zahl überspringen, wird das die CLR ignorieren und einfach zur nächsten verfügbaren Indexzahl springen). Dies ist ein wichtiges Merkmal der Benutzeroberfläche, das man berücksichtigen muss. Es mag aber Situationen geben, in denen Sie nicht wünschen, dass der Benutzer mit der (ÿ_)-Taste auf ein Steuerelement springt. In der Regel sollten z.B. Benutzer nicht in der Lage sein, ein Label-Steuerelement zu manipulieren, daher sollte es nicht auswählbar sein. In solchen Fällen setzen Sie die TabStop-Eigenschaft auf false.
227
Windows Forms mit Steuerelementen erweitern
Abbildung 6.14: Das Modifizieren der Anchor- und Dock-Eigenschaften einer Schaltfläche.
6.14 Zusammenfassung Am besten verwenden Sie dieses umfangreiche Kapitel als eine Art Nachschlagewerk und Referenz – Sie müssen das nicht alles auswendig lernen. Sie haben mehrere Schaltflächen kennen gelernt: Button, RadioButton und CheckBox. Obwohl sie sich in ihrer Implementierung leicht unterscheiden, gestatten sie alle dem Benutzer, eine Auswahl zu treffen. Die gebräuchlichsten Mitglieder diese Steuerelemente sind die Text-Eigenschaft und das Click-Ereignis. Mit Hilfe des DateTimePicker-Steuerelements kann der Benutzer Zeiten und Daten wählen, indem er sie entweder manuell eingibt oder aus der Dropdown-Liste des Kalenders auswählt. Was uns zum nächsten Steuerelement führt: der ComboBox. Sie ist eine Kombination aus TextBox und ListBox; ein Benutzer kann Text von Hand eingeben oder Elemente aus einer Dropdown-Liste auswählen. Anders als die ComboBox erlaubt es die ListBox dem Benutzer, mehrere Elemente auf einmal auszuwählen. Dieses Steuerelement ist sehr nützlich, wenn Sie dem Benutzer mehrere Auswahlmöglichkeiten anbieten wollen. ImageList und PictureBox haben beide mit Bildern zu tun, unterscheiden sich aber stark voneinander. ImageList ist im Grunde ein Array von Grafiken, das Sie mit anderen Steuerelementen verknüpfen können, um Grafiken anzuzeigen (so wie die Schaltflächen für eine ToolBar). Eine PictureBox wird lediglich dazu verwendet, dem Benutzer eine einzelne Grafik anzuzeigen.
228
Fragen und Antworten
Mit Hilfe von TabControl können Sie Steuerelemente auf logische Weise gruppieren, indem Sie Registerkarten (Tabseiten) verwenden. Die Steuerelemente TextBox und RichTextBox bieten dem Benutzer die Möglichkeit, Text in ein Feld einzugeben. Letzteres bietet den weitaus größeren Funktionsumfang: Der Benutzer kann u.a. Schriftart, Farbe, Größe und Ausrichtung des Textes ändern. Dieses Steuerelement ähnelt WordPad. Mit Hilfe des Timer-Steuerelements lassen sich Ereignisse in bestimmten Zeitabständen auslösen. Unter anderem können Sie damit Weckuhren erstellen oder automatisierte Datensicherungsprozeduren durchführen lassen. Mit TreeView können Sie hierarchisch angeordnete Informationen in einer einfach zu bedienenden Strukturansicht anzeigen. Zum Schluss haben Sie gelernt, wie Sie das Layout Ihrer Steuerelemente in Formularen steuern, indem Sie Dock, Anchor, TabIndex und TabStop verwenden. Die ersten beiden Eigenschaften führen ähnliche Funktionen aus, der Unterschied besteht darin, dass Dock ein Steuerelement an den Rand eines Formulars bewegt, Anchor aber die Position eines Steuerelements relativ zu den Rändern des Formulars fixiert – hier ist kein Andocken an den Rand nötig.
6.15 Fragen und Antworten F
GDI+ wurde heute mehrmals erwähnt. Um was handelt es sich dabei? A
GDI+ ist die erweiterte Schnittstelle zu Grafikgeräten (Graphics Device Interface, GDI), mit deren Hilfe Sie verschiedene Grafiktypen zeichnen und bearbeiten können und die Sie in die Lage versetzt, Zeichenverfahren in Windows zu handhaben. GDI+ wurde entwickelt, um Entwicklern zu ermöglichen, sich auf die Grafikfunktionen zu konzentrieren, ohne dabei an die vielen unterschiedlichen Typen von Grafik-Hardware (wie etwa 3D-Grafikkarten) und Treiber denken zu müssen. GDI+ ist das Hauptthema an Tag 13.
F
Verwendet Microsoft Word ein RichTextBox-Steuerelement als seine Benutzeroberfläche? A
Nein. Word verwendet eine besondere, speziell dafür entwickelte Steuerelementart, die der Öffentlichkeit nicht zugänglich ist. Rich Text wird als normaler Text dargestellt, doch mit speziellem Code für die Formatierung versehen. Word benutzt für das Speichern seiner Dateien ein Binärformat, das sie nur für Word brauchbar macht. Wenn Sie also eine Textverarbeitung mit dem gleichen Funktionsreichtum wie Word erstellen wollen, müssen Sie entweder Ihr eigenes Steuerelement schreiben oder auf dem RichTextBox-Steuerelement aufbauen.
229
Windows Forms mit Steuerelementen erweitern
6.16 Workshop Dieser Workshop soll Ihnen helfen, sich besser an die heute behandelten Begriffe zu erinnern. Es hilft Ihnen sehr, die Antworten vollständig zu verstehen, bevor Sie weiterlesen. Die Antworten zu den Quizfragen und den Übungen finden Sie in Anhang A.
Quiz 1. Wodurch werden RadioButton-Auswahlen begrenzt? 2. Was bedeutet die folgende Datums-/Zeit-Formatzeichenfolge? "hh:MM:yy-dddd"
3. Welches Objekt ist mit dem TreeView-Steuerelement verknüpft? 4. Setzen Sie für ein Button-Steuerelement namens btHappy Eigenschaften, die es dazu veranlassen, senkrecht zu expandieren, sobald sein Containerformular seine Größe ändert. 5. Nennen Sie zehn Mitglieder, die allen heute besprochenen Steuerelementen gemeinsam sind! 6. Wie heißen die fünf Mitglieder des Timer-Steuerelements? 7. Wie fügt man dem Steuerelement TabControl Tabseiten hinzu? Beschreiben Sie dies in Worten und mit Hilfe einer Zeile Code. 8. Welche beiden Methoden sollte man ausführen, wenn man vorhat, einer ComboBox mit Hilfe der Add-Methode viele Elemente hinzuzufügen? Wenn Sie an Ihr Wissen über Windows Forms-Steuerelemente denken: Welche anderen heute vorgestellten Steuerelemente haben Ihrer Ansicht nach ebenfalls diese Methoden? 9. Wahr oder falsch? Das PictureBox-Steuerelement kann nur Bitmap- (.BMP) und GIFGrafiken anzeigen.
Übung Erstellen Sie in C# eine Anwendung, mit deren Hilfe Benutzer ihre CD-Sammlung katalogisieren und betrachten können. Sie müssen neue Künstler/Alben/Stücke in den Katalog aufnehmen und mit einer hierarchischen Liste betrachten können. (Kümmern Sie sich nicht um das Speichern der Eingaben – das besprechen wir an Tag 9, wenn es um ADO.NET geht.) Denken Sie daran, dass Alben nur Künstlern hinzugefügt werden und Stücke nur Alben.
230
Mit Dialogfeldern arbeiten
7
Mit Dialogfeldern arbeiten
Dialogfelder sind eine spezielle Art von Windows Forms, die hauptsächlich für die Interaktion mit dem Benutzer und die Anzeige von Meldungen eingesetzt wird. Während der Arbeit mit Windows haben Sie wahrscheinlich bereits Tausende davon gesehen, so etwa wenn Sie sich in Windows einloggen wollen. Dialogfelder sind ein sehr vielseitiges Werkzeug für Windows Forms und bieten zahlreiche komplexe Optionen für Ihre Anwendungen und deren Benutzer. Wir werden den ganzen Tag damit verbringen, den nicht ganz einfachen Umgang mit Dialogfeldern und den speziellen Dialogarten in Windows Forms zu erlernen. Heute erfahren Sie etwas über die verschiedenen Arten von Dialogfeldern in Windows und wie Sie 쐽
das »Ergebnis« eines Dialogfeldes bestimmen,
쐽
jedes beliebige Dialogfeld anpassen,
쐽
mit Hilfe von Dialogfeldern häufig auftretende Aufgaben in Windows bewältigen.
7.1
Informationen holen und anzeigen
Die wichtigste Aufgabe eines Dialogfeldes besteht darin, den Benutzer aufzufordern, Informationen einzugeben. Dies geschieht auf eine ganz andere Weise als bei Ihrem zentralen Windows Formular-Objekt, denn Sie können die Aufmerksamkeit des Benutzers auf recht direkte Weise gewinnen. Während Benutzer in Ihrem Formular häufig die Bitte um Information nur überfliegen, können Sie hingegen recht sicher sein, dass sie die angeforderten Informationen in einem Dialogfeld eingeben. Da Dialogfelder sich gut dazu eignen, die Aufmerksamkeit des Benutzers zu fesseln (Windows begleitet die erste Anzeige eines Dialogfeldes häufig mit einem Warnton), scheinen sie auch geradezu perfekt für das Anzeigen wichtiger Informationen zu sein. Im gestrigen Beispiel einer Weckuhr haben Sie das Meldungsfeld, einen speziellen Dialogfeldtyp, verwendet, um dem Benutzer eine Weckmeldung zukommen zu lassen. In ähnlicher Weise haben Sie in der gestrigen Übung, dem Aufbau eines Musikkatalogs, den Benutzer per Dialogfeld darauf hingewiesen, keine Objekte der falschen Kategorie zuzuordnen (etwa einen Künstler einem Album hinzuzufügen statt andersherum). Ob nun beim Anzeigen oder Einholen von Information, ein Dialogfeld schickt stets Daten zurück an das Hauptformular (mehr dazu später). Nachdem die erbetenen Informationen beschafft wurden, kann der Aufrufer des Dialogfeldes, normalerweise ein Form-Objekt, diese Informationen dazu verwenden, um einige Aktionen auszuführen. Wenn Sie beispielsweise in Word ein Dokument bearbeiten und versuchen, es ohne Speichern zu schließen, erscheint ein Dialogfeld, die Sie zum Spei-
232
Informationen holen und anzeigen
chern auffordert, wie in Abbildung 7.1 zu sehen. Je nachdem auf welche Schaltfläche der Benutzer klickt (JA, NEIN oder ABBRECHEN), führt Word die angemessene Aktion aus (also jeweils speichern, nicht speichern oder das Schließen des Dokuments abbrechen). Auf diese Weise zeigt das Dialogfeld einerseits eine Meldung an und holt auf der anderen Seite Informationen ein.
Abbildung 7.1: Dieses Dialogfeld in Microsoft Word erfordert eine Reaktion vom Benutzer.
Wie Sie heute weiter unten sehen werden, können Dialogfelder weitaus komplizierter sein, als dieses einfache Beispiel vermuten lässt. Sie bieten Ihnen als Entwickler eine Fülle von Möglichkeiten, Informationen zu beschaffen, und das Beste daran ist, dass .NET bereits die meisten komplizierten Arbeiten für Sie erledigt hat. Im Grunde ist ein Dialogfeld ein Form-Objekt, dessen FormBorderStyle-Eigenschaft auf FixedDialog gesetzt ist. (Wie Sie von Tag 3 noch wissen, bedeutet dies, dass das Fenster genau wie ein typisches Windows Forms-Fenster aussieht, nur dass sich seine Größe nicht ändern lässt.) Zusammen mit einer Eigenschaft, die als Modalität bezeichnet wird (mehr dazu später), macht dies Dialogfelder sehr effizient beim Einholen von Benutzereingaben. Der Benutzer kann nicht mit seiner Tätigkeit fortfahren, bis er das Dialogfeld beantwortet hat, um sie »aus dem Weg zu schaffen«. Obwohl es zutrifft, dass die einfachsten Dialogfelder nur etwas Text und zwei Schaltflächen für OK und ABBRECHEN aufweisen, sollte Sie dies nicht davon abhalten, dem Dialogfeld eigene Steuerelemente hinzuzufügen. Sie können sogar jedes Windows FormsSteuerelement – selbst Menüs und Statusleisten – integrieren, als ob es ein Form-Objekt wäre. Doch wozu sollte man ein Dialogfeld zu einer kompletten Anwendung ausbauen? Sicher nicht bis in Word-Dimensionen, doch vielleicht bis zur Komplexität eines Taschenrechners oder eines Temperaturumrechners (Fahrenheit in Celsius und umgekehrt). Doch letzten Endes läuft es auf Folgendes hinaus: Forms wurden dafür entwickelt, als Anwendungen zu agieren, und Dialogfelder nicht. Ich empfehle
Bitte beachten Sie
Verwenden Sie wie bisher Forms für die Anwendungserstellung, es sei denn, Sie hätten einen triftigen Grund, der dagegen spräche.
Verlassen Sie sich nicht nur auf Dialogfelder, um Benutzereingaben zu erhalten. Statt dessen sollten Sie sie nur einsetzen, um dem Benutzer etwas mitzuteilen.
233
Mit Dialogfeldern arbeiten
7.2
Unterschiedliche Arten von Dialogen
Am gebräuchlichste ist das OK/ABBRECHEN-Dialogfeld, doch es sind noch einige andere Typen verfügbar. Das .NET Framework stellt verschiedene Dialogfelder für die Mehrzahl der üblichen Windows-Aufgaben, die Benutzereingaben erfordern, zur Verfügung. Es steht Ihnen sogar frei, einen Benutzer dazu zu zwingen, auf ein Dialogfeld zu antworten, bevor er irgendetwas anderes tun kann. Wir wollen einen Blick auf die Eigenschaften werfen, die Dialogfelder steuern.
Modal vs. nichtmodal Ein modales Formular oder Dialogfeld fordert eine Eingabe vom Benutzer, bevor er mit seiner Tätigkeit in der Anwendung fortfahren kann. Ein Beispiel dafür ist die »Änderungen speichern?«-Abfrage in Word. Erscheint sie, kann man in Word nichts anderes tun als sie zu beantworten. Versucht man in das Hauptdokumentfenster zurückzugehen, erhält man einen Fehlerton und das Dialogfeld fordert erneut zur Antwort auf. Daher erfordert ein modaler Dialog eine Benutzereingabe. Modale Dialoge halten Sie zwar in der jeweiligen Anwendung auf, Sie können aber jederzeit zu einer anderen Anwendung wechseln, etwa zu Excel oder Photoshop. Ein nichtmodales Dialogfeld ist das genaue Gegenteil: Es verlangt keine Benutzereingabe, und der Benutzer kann zwischen der Anwendung und dem Dialog nach Belieben hin und her wandern. Der typische Einsatzbereich dafür ist ein Hilfe-Fenster, das sich neben der Anwendung positioniert und häufig auftretende Fragen beantwortet. Solche Dialoge verlangen nicht, dass Sie mit ihnen interagieren, obwohl Sie das natürlich tun können. Ein gutes Beispiel wäre der Microsoft Office-Assistent in Form einer Büroklammer. Es ist für den Entwickler oftmals schwieriger, mit nichtmodalen Dialogen umzugehen als mit modalen, denn er muss praktisch zwei Formulare gleichzeitig überwachen und passende Daten hin und her leiten. Bei einem modalen Dialog hingegen hat man es entweder mit dem Dialogfeld oder mit der Anwendung zu tun, nie aber mit beiden zugleich, was die Sache erleichtert. Aus diesem Grund werden nichtmodale Dialoge fast ausschließlich für die Informationspräsentation eingesetzt, aber nicht für die Datenbeschaffung.
234
Dialogfelder
Die Standarddialogfelder Standarddialogfelder (Common Dialog Boxes) dienen dazu, allgemein übliche Aufgaben vorzubereiten wie etwa das Öffnen und Speichern einer Datei, die Auswahl einer Schriftart usw. Da sie für häufige Aufgaben eingesetzt werden, sind sie an vielen Stellen anzutreffen – man denke nur an jede Anwendung, die das Öffnen einer Datei erlaubt. Sie setzt die gleiche Benutzeroberfläche für diese Aufgabe ein wie viele andere Anwendungen auch: ein Standarddialogfeld. Für den Entwickler ist dies enorm hilfreich, denn er muss für jede Anwendung, die er schreibt, keine eigenes Dialogfeld programmieren. In .NET Framework sind bereits sieben Klassen für Standarddialogfelder fix und fertig enthalten: 쐽
ColorDialog: Damit kann der Benutzer eine Farbe aus einer vorgegebenen Palette aus-
wählen (etwa um die Farbe einer Schriftart zu ändern). 쐽
FontDialog: Damit kann der Benutzer eine Schriftart auswählen.
쐽
OpenFileDialog: Damit kann der Benutzer eine zu öffnende Datei auswählen. Das
Dialogfeld öffnet die Datei nicht selbst, sondern erlaubt nur ihre Auswahl; Sie müssen in Ihrer Anwendung dafür sorgen, die ausgewählte Datei zu holen und zu öffnen; das Dialogfeld erledigt das nicht automatisch. 쐽
PageSetupDialog: Damit kann der Benutzer in Windows Dokumente formatieren (Sei-
tenränder einstellen, Papiergröße wählen usw.). 쐽
PrintDialog: Damit kann der Benutzer einen Drucker auswählen, Druckoptionen einstellen und ein Dokument drucken.
쐽
PrintPreviewDialog: Seiten- oder Druckvorschau. Damit kann der Benutzer prüfen, wie ein Dokument auf Papier aussehen wird, bevor er es ausdruckt.
쐽
SaveFileDialog: Damit kann der Benutzer Dateien in einem ausgewählten Ordner
speichern (gleicht dem »SPEICHERN UNTER...«-Dialog in Word). Diese Klassen verhalten sich genau wie andere Windows Forms-Steuerelemente oder -Dialogfelder, daher sollten Sie sie rasch erlernen können. Wir kommen heute noch auf sie zurück. Wir wollen zunächst sehen, wie man einfache Dialogfelder verwendet.
7.3
Dialogfelder
Das einfachste Dialogfeld ist die MessageBox-Klasse (Meldungsfeld). Sie verfügt über ein einziges (nicht geerbtes) Mitglied, die Show-Methode, die das Dialogfenster, das nur eine OK-Schaltfläche aufweist, auf den Bildschirm bringt:
235
Mit Dialogfeldern arbeiten
MessageBox.Show("Hallo Welt")
Diese Codezeile zeigt ein Dialogfenster mit dem Text »Hallo Welt« an sowie eine OKSchaltfläche. Für eine Reihe von Situationen reicht dieser Funktionsumfang aus. Die Show-Methode verfügt jedoch noch über einige Parameter, mit denen Sie sie Ihren Wünschen anpassen können. Sie können eine Instanz der MessageBox nicht direkt erzeugen. Der folgende Code etwa erzeugt einen Fehler: dim msgMyMessage as New MessageBox
Der einzige Weg, auf dem Sie mit der MessageBox interagieren können, besteht im Aufruf ihrer Show-Methode: MessageBox.Show("Hallo Welt!")
Tabelle 7.1 führt die verschiedenen Parameter für die Show-Methode auf, und zwar in der Reihenfolge, in der sie im Methodenaufruf stehen müssen (bis auf Nummer 2 sind alle Parameter optional). Nr. des Parameters
Typ
Beschreibung
1
IWin32Window
Das Fenster, vor dem das Dialogfeld angezeigt werden soll (z.B. Me in VB .NET oder this in C#).
2
String
Der im Dialogfenster anzuzeigende Text. Dies ist der einzige obligatorische Parameter.
3
String
Der in der Titelzeile anzuzeigende Text.
4
MessageBoxButtons
Anzahl und Typ der anzuzeigenden Schaltflächen.
5
MessageBoxIcon
Das im Dialogfenster anzuzeigende Symbol.
6
MessageBoxDefaultButton
Legt die (vorausgewählte) Standardschaltfläche fest.
7
MessageBoxOptions
Diverse Optionen für das Dialogfeld.
Tabelle 7.1: Parameter für die Methode MessageBox.Show
Sie könnten die MessageBox auf folgende Art und Weise anzeigen: MessageBox.Show(Me, "Hallo Welt", "Mein Dialogfeld") MessageBox.Show("Hallo Welt", "Mein Dialogfeld", MessageBoxButtons.OKCancel) MessageBox.Show(Me, "Hallo Welt", "Mein Dialogfeld", MessageBoxButtons.OKCancel, MessageBoxIcon.Hand)
236
Dialogfelder
Diese drei Aufrufe erzeugen die drei Dialogfelder, die Sie in Abbildung 7.2sehen. Abbildung 7.2: Sie können einem Meldungsfeld auf einfache Weise weitere (bis zu drei) Schaltflächen hinzufügen.
Die ersten drei in Tabelle 7.1 aufgeführten Parameter sind leicht zu verstehen. Die vier letzten hingegen sind Aufzählungen, die Anzeigeoptionen für das Dialogfeld angeben. Die MessageBoxButtons-Aufzählung enthält die Werte AbortRetryIgnore, OK, OKCancel, RetryCancel, YesNo und YesNoCancel. Der Zweck jeder einzelnen ist einfach herauszufinden: AbortRetryIgnore zeigt drei Schaltflächen an, die die Aufschriften BEENDEN, WIEDERHOLEN und IGNORIEREN tragen; OK zeigt einfach eine Schaltfläche mit OK an, usw. MessageBoxIcon zeigt neben dem Text im Meldungsfenster ein Symbol an. Die Werte können in Asterisk, Error, Exclamation, Hand, Information, None, Question, Stop oder Warning
bestehen. Die tatsächlich angezeigten Symbole können von Betriebssystem zu Betriebssystem variieren (in Windows sind es oftmals die gleichen). Die Abbildung 7.3 zeigt jedes dieser Symbole in Windows NT.
Abbildung 7.3: Der neunte Wert der MessageBoxIconAufzählung lautet None (keines). MessageBoxDefaultButton ist eine leicht zu erlernende Eigenschaft – sie besitzt nur drei Werte: Button1, Button2 und Button3. Die Eigenschaft legt fest, welche Schaltfläche im
Dialogfenster den voreingestellten Fokus hat. Diese Schaltfläche ist vorausgewählt, wenn das Dialogfeld angezeigt wird, und ist auch diejenige, die angeklickt wird, falls der Benut-
237
Mit Dialogfeldern arbeiten
zer die (¢)-Taste drückt. (Nochmals: Es passen maximal drei Schaltflächen in ein Meldungsfeld.) MessageBoxOptions schließlich besitzt vier nicht miteinander verwandte Werte. DefaultDesktopOnly und ServiceNotification geben an, dass das Meldungsfeld auf dem gerade
aktiven Bildschirm erscheinen soll (falls der Benutzer mehr als einen Monitor einsetzt). RightAlign gibt vor, dass der Text im Meldungsfeld rechtsbündig ausgerichtet sein soll, und RtlReading gibt an, dass der Text so formatiert sein soll, dass man ihn von rechts nach links lesen kann (was etwa in arabischen Ländern hilfreich ist). Mit Hilfe der Or- und |Operatoren können Sie diese Werte miteinander kombinieren. MessageBox.Show("Hallo Welt","Mein Dialogfeld"", MessageBoxButtons.OKCancel, MessageBoxIcon.None, MessageBoxDefaultButton.Button1, MessageBoxOptions.RightAlign Or MessageBoxOptions.ServiceNotification)
Eigene Dialogfelder erstellen Die MessageBox-Klasse ist ein ausgezeichnetes vorgefertigtes Dialogsteuerelement, das eine ganze Reihe von Optionen anbietet. Doch in manchen Fällen genügt die MessageBox Ihren Ansprüchen nicht. In diesen Fällen können Sie Ihre eigene Dialogfeldklasse erstellen. Diese aufzubauen entspricht fast dem Aufbau einer Windows Forms-Klasse; man erbt von System.Windows.Forms.Form, erstellt Steuerelemente, fügt sie dem Formular hinzu und sieht auch Ereignishandler vor. Der einzige Unterschied besteht darin, dass man dem übergeordneten Formular (das das Dialogfeld anzeigt) alle notwendigen Eigenschaften zur Verfügung stellt. Lassen Sie uns als Beispiel einmal das DATEI/ÖFFNEN-Dialogfeld von Microsoft Word, das in Abbildung 7.4 zu sehen ist, auf seine Bestandteile hin untersuchen.
Abbildung 7.4: Das Dialogfeld zum Öffnen von Dateien enthält verschiedene Arten von Steuerelementen.
238
Dialogfelder
Sie vermuten sicher schon, welche Arten von Steuerelementen nötig sind, um dieses Dialogfenster aufzubauen: eine ComboBox für die Dropdown-Listen der Verzeichnisse und der Dateitypen, eine Reihe von Button-Steuerelementen für die ÖFFNEN- und ABBRECHENSchaltflächen, ein paar Label-Steuerelemente, möglicherweise ein TreeView-Steuerelement und ein paar TextBox-Steuerelemente. Sobald der Benutzer eine Datei ausgewählt hat, will die Hauptanwendung nur eines wissen: Welche Datei ist das? Daraufhin wird der ausgewählte Dateiname im Textfeld DATEINAME angezeigt. Daher muss die Hauptanwendung die Eigenschaft Text dieser TextBox wissen, damit sie die Datei angemessen behandeln kann. Um diese Eigenschaftstypen dem Hauptformular offen zu legen, müssen wir Eigenschaften erstellen (ähnlich wie die Text-Eigenschaft). Listing 7.1 zeigt eine benutzerdefinierte Dialogfeldklasse, mit der der Benutzer die Titelzeile seines Formulars ändern kann. Listing 7.1: Ihre erste Anwendung mit einem benutzerdefinierten Dialogfeld 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29:
Imports System Imports System.Windows.Forms Imports System.Drawing namespace TYWinForms.Day7 public class ChangeCaption : Inherits Form private btnChange as new Button private tbCaption as new TextBox public readonly property Caption as String Get Return tbCaption.Text End Get end property public sub New tbCaption.Location = new Point(10,10) btnChange.Text = "Ändern!" btnChange.Location = new Point(125,10) AddHandler btnChange.Click, new EventHandler(AddressOf Me.Clicked) Me.Text = "Überschrift ändern" Me.Size = new Size(250,100) Me.Controls.Add(btnChange) Me.Controls.Add(tbCaption) end sub
239
Mit Dialogfeldern arbeiten
30: 31: 32: 33: 34:
private sub Clicked(Sender as Object, e as EventArgs) Me.Hide end sub end class End Namespace
Speichern Sie diese Datei als listing7.1.vb. Auf den ersten Blick sieht der Code wie jede andere Windows Forms-Anwendung aus. In den Zeilen 8 und 9 erstellen Sie zwei Steuerelemente, richten in den Zeilen 18 bis 27 Eigenschaften in einem Konstruktor ein und erstellen einen Ereignishandler in den Zeilen 30 bis 32. Der Hauptunterschied besteht darin, dass es in diesem Code keine Main-Methode gibt. Das liegt daran, dass diese Klasse niemals selbstständig ausgeführt wird – sie sollte nur in einem Formular oder einer Anwendung verwendet werden. Werfen Sie einen Blick auf Zeile 30 und den Ereignishandler. Er tut nur eines, nämlich die Hide-Methode des Formulars aufzurufen. Ohne dies würde nichts passieren, sobald der Benutzer auf die Schaltfläche »Titelzeile ändern« geklickt hat. Da wir ein modales Dialogfeld erstellen wollen, würde dies bedeuten, dass der Benutzer nicht wieder zu der ursprünglichen Anwendung zurückkehren könnte. Indem Hide aufgerufen wird, bringen wir das Dialogfeld zum Verschwinden und holen statt dessen die ursprüngliche Anwendung in den Vordergrund, so dass wieder eine Interaktion möglich ist. Beachten Sie schließlich die Zeilen 11 bis 15, die das Erstellen von Eigenschaften in einer Klasse illustrieren. Die Deklaration ähnelt der einer Funktion, verwendet aber das Schlüsselwort property statt function. Innerhalb dieser Eigenschaft finden sich zwei besondere Schlüsselwörter: Get und Set. Der Code in Get wird ausgeführt, sobald eine andere Klasse auf diese Eigenschaft zugreifen will. Set hingegen wird eingesetzt, wenn eine andere Klasse für diese Eigenschaft einen Wert setzt. In unserem Fall brauchen wir jedoch keinen Set-Teil, daher schreiben wir nur den für Get und sorgen dafür, dass die Eigenschaft als readonly (Zeile 11) markiert ist. In die Get- und Set-Teile können Sie jeden beliebigen Code einfügen; hier jedoch wollen wir nur den Wert zurückgeben, der sich in der TextBox befindet, wobei wir das Return-Schlüsselwort verwenden. In C# würde diese Eigenschaft wie in folgendem Codestück aussehen: public string Caption { get { return tbCaption.Text; } }
Beachten Sie, dass C# nicht die Schlüsselwörter property oder readonly erfordert. Wenn Sie die Eigenschaft value setzen wollen, verwenden Sie das Schlüsselwort value:
240
Dialogfelder
'VB .NET Set tbCaption.Text = value End Set //C# set { tbCaption.Text = value; }
Die einzigen public-Mitglieder in der ChangeCaption-Klasse sind der Konstruktor und die Caption-Eigenschaft. Da wir nicht wünschen, dass andere Klassen direkt auf den Rest der Mitglieder zugreifen, machen wir sie alle private. Sie können diese Vorgehensweise bei der Deklaration von public- und privateVariablen auch für die Erstellung von Eigenschaften Ihrer regulären Klassen verwenden. Wir wollen uns die Anwendung ansehen, die diese benutzerdefinierte Dialogfeldklasse verwenden soll. Listing 7.2 zeigt eine einfache Applikation, mit der der Benutzer die Titelzeile des Formulars ändern kann, indem er das ChangeCaption-Dialogfeld aus Listing 7.1 sowie dessen Caption-Eigenschaft einsetzt. Listing 7.2: Die Verwendung des benutzerdefinierten Dialogfeldes 1: Imports System 2: Imports System.Windows.Forms 3: Imports System.Drawing 4: 5: namespace TYWinForms.Day7 6: 7: public class Listing72 : Inherits Form 8: private btnChange as new Button 9: 10: public sub New 11: btnChange.Text = "Überschrift ändern" 12: btnChange.Location = new Point(75,75) 13: btnChange.Size = new Size(100,75) 14: AddHandler btnChange.Click, new EventHandler(AddressOf Me.ShowChangeBox) 15: 16: Me.Text = "Überschrift ändern" 17: Me.Controls.Add(btnChange) 18: end sub 19: 20: public sub ShowChangeBox(Sender as Object, e as EventArgs) 21: dim dlgChangeCaption as new ChangeCaption 22: dlgChangeCaption.ShowDialog
241
Mit Dialogfeldern arbeiten
23: 24: 25: 26: 27: 28: 29: 30: 31:
Me.Text = dlgChangeCaption.Caption end sub public shared sub Main() Application.Run(new Listing72) end sub end class End Namespace
Speichern Sie diese Datei unter dem Namen listing7.2.vb. Listing 7.2 zeigt eine weitere einfache Windows Forms-Klasse, nur dass diese die Hauptanwendung darstellt und daher eine Main-Methode aufweist. Die Zeilen 1 bis 18 sind typisch; sie erstellen und instantiieren das Formular und seine Steuerelemente. Erst in Zeile 20 beginnt es interessant zu werden. Die Methode ShowChangeBox behandelt das Click-Ereignis der Schaltfläche btnChange. In Zeile 21 erzeugen Sie eine neue Instanz der in Listing 7.1 erstellten ChangeCaption-Klasse. Beachten Sie, dass dieses Vorgehen dem beim Erstellen jedes anderen .NET-Objekts entspricht. In Zeile 22 rufen Sie die ShowDialog-Methode auf, die das Dialogfenster zur modalen Anzeige veranlasst.
Moment mal! In Ihrer ChangeCaption-Klasse haben Sie keine ShowDialogMethode erstellt. Wo kam sie also her? Die Klasse System.Windows.Forms.Form enthält die ShowDialog-Methode, weshalb Ihre Dialogfeldklasse sie erben konnte. Das bedeutet aber auch, dass absolut jedes Windows Form-Formular als Dialogfeld eingesetzt werden kann. Das bietet Ihnen eine Menge Flexibilität, wenn Sie Ihre Anwendungen schreiben. Nachdem Zeile 22 ausgeführt und das Dialogfeld angezeigt wurde, geht die Kontrolle über die Anwendung an das modale Dialogfeld über – bis dieses Fenster wieder geschlossen wird, kann die Hauptanwendung nichts tun. Sobald Sie etwas in das Dialogfeld eingeben und die Schaltfläche »ÄNDERN!« anklicken, geht die Kontrolle wieder an die Hauptanwendung über. Die Ausführung geht in Zeile 24 weiter, die die Caption-Eigenschaft des Dialogfeldes holt und sie der Text-Eigenschaft des aktuellen Formulars zuweist. Nun verstehen Sie, wie die Caption-Eigenschaft aus Listing 7.1 eingesetzt wird. Da diese beiden Listings in unterschiedlichen Dateien gespeichert sind, müssen Sie beide zu einer Anwendung kompilieren. Mit Hilfe von Visual Studio .NET ist dies einfach: Sorgen Sie nur dafür, dass sich beide Dateien im selben Projekt befinden und kompilieren Sie sie dann ganz normal (indem Sie ERSTELLEN aus dem ERSTELLEN-Menü auswählen).
242
Dialogfelder
Wenn Sie den Befehlszeilencompiler verwenden, geben Sie nur die beiden Dateien im Befehl an: vbc /t:winexe /r:system.windows.forms.dll /r:system.drawing.dll listing7.1.vb listing7.2.vb
Solange sich diese beiden Dateien im selben Ordner befinden, funktioniert dieser Befehl und Sie erhalten eine ausführbare Datei namens Listing7.1.exe (die Reihenfolge, die Sie für die zu kompilierenden Dateien angeben, bestimmt den Namen der ausführbaren Datei). Das war's schon. Abbildung 7.5 zeigt das Ergebnis.
Abbildung 7.5: Es ist recht einfach, ein benutzerdefiniertes modales Dialogfeld zu erstellen.
Beachten Sie, dass Sie keine besonderen Eigenschaften für Ihre benutzerdefinierte Dialogfeldklasse einrichten mussten. Indem Sie die ShowDialog-Methode aufrufen, stellt die CLR sie automatisch als modales Dialogfeld dar. Doch genau wie in einem gewöhnlichen Formular können Sie Eigenschaften beliebig modifizieren, so etwa SizeGripStyle, um den Größenziehpunkt in der unteren rechten Ecke des Formulars zu ändern, oder ControlBox, um die rechts oben verfügbaren Steuerschaltflächen festzulegen. Wollen Sie Ihr Dialogfeld hingegen nichtmodal präsentieren, rufen Sie statt ShowDialog die Methode Show auf. Der Benutzer wird zwischen Dialogfenster und Hauptanwendung hin und her wechseln können. Doch in der soeben erstellten Anwendung würde dies eine Reihe von Problemen verursachen: 쐽
Da die Ausführung im Hauptformular nicht angehalten wird, sobald die Show-Methode aufgerufen wurde, wird Zeile 24 des Listing 7.2 (wo die Titelzeile aus dem Dialogfeld geholt wird) sofort ausgeführt und die Titelzeile des Formulars wird sich in eine leere Zeichenfolge verwandeln. Denn schließlich ist bei der Dialogfelderstellung das Textfeld tbCaption leer, und Zeile 24 wird ausgeführt, bevor Sie dazukommen, Text einzutragen und auf die ÄNDERN!-Schaltfläche zu klicken.
쐽
Der Benutzer kann fortwährend auf die Schaltfläche TITELZEILE ÄNDERN im Hauptformular klicken, was dazu führt, dass viele Dialogfelder auf einmal angezeigt werden.
243
Mit Dialogfeldern arbeiten
Wenn der Benutzer Text in das tbCaption-Feld eingibt und die Schaltfläche ÄNDERN! anklickt, verschwindet in diesem Fall das Dialogfeld und sonst passiert nichts; die Titelzeile des Formulars ändert sich nicht, denn Zeile 24 wurde bereits lange vor dem Anklicken der ÄNDERN!-Schaltfläche ausgeführt. Um also die Titelzeile dennoch zu bestimmen, müssen Sie irgendwie auf die Eigenschaft tbCaption.Text des Dialogfeldes zugreifen. Dies gelingt, wenn Sie eine neue public-Eigenschaft erstellen: Public property Caption As String Get return tbCaption.Text End Get Set tbCaption.Text = value End Set End Property
Die Lösung für das zweite Problem besteht im Einsatz eines modalen Dialogfeldes. Doch dann muss der Benutzer das Dialogfeld explizit schließen, bevor ihm erlaubt ist, zum Hauptformular zurückzukehren. Sorgen Sie dafür, dass Sie nur modale Dialogfenster verwenden, wenn Sie diese Funktionsweise nutzen wollen.
Informationen abrufen Wir haben bereits gesehen, wie man mit Hilfe von Eigenschaften Daten aus einem Dialogfeld beschafft. Doch manchmal reichen Eigenschaften nicht aus, um jede gewünschte Information zu bekommen. Gelegentlich müssen Sie auch wissen, auf welche Weise der Benutzer das Dialogfeld geschlossen hat: Hat er auf die OK-Schaltfläche geklickt? Oder doch auf die ABBRECHEN-Schaltfläche? Diese Fakten, die man als das Ergebnis (result) des Dialogfeldes bezeichnet, können die Arbeitsweise Ihrer Anwendung verändern, so dass es nötig ist, auch diese Werte zu beschaffen. Die Aufzählung DialogResult enthält mehrere Werte, mit deren Hilfe Sie das Ergebnis eines Dialogfeldes bestimmen. Die ShowDialog-Methode gibt einen Wert aus dieser Aufzählung zurück, so dass Sie damit tun können, was Sie wollen, etwa so: dim objDR as DialogResult objDR = dlgChangeCaption.ShowDialog if objDR = DialogResult.OK then 'die Titelzeile ändern elseif objDR = DialogResult.No then 'nichts tun end if
244
Dialogfelder
Je nach dem vom Dialogfeld gelieferten Ergebnis können Sie eine bedingte Ausführung beginnen. Für jede Schaltfläche in einem Dialogfeld gibt es einen DialogResult-Wert (und noch mehr): 쐽
Abort/Beenden
쐽
Cancel/Abbrechen
쐽
Ignore/Ignorieren
쐽
No/Nein
쐽
None/Kein
쐽
OK
쐽
Retry/Wiederholen
쐽
Yes/Ja
Listing 7.3 zeigt ein Beispiel dafür, wie man DialogResult in einem Meldungsfeld einsetzt. Listing 7.3: Das Ergebnis eines Dialogfeldes abrufen (in C#) 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19:
using System; using System.Windows.Forms; using System.Drawing; namespace TYWinForms.Day7 { public class Listing73 : Form { public Listing73() { if (MessageBox.Show("Wünschen Sie eine Vollbilddarstellung dieser Anwendung?", "Listing 7.3", MessageBoxButtons.YesNo) == DialogResult.Yes) { this.Size = Screen.GetWorkingArea(this).Size; this.DesktopLocation = new Point(0,0); } } public static void Main() { Application.Run(new Listing73()); } } }
In Zeile 9 prüfen Sie das Ergebnis der MessageBox.Show-Methode. Wenn es dem DialogResult.Yes-Wert entspricht (der Benutzer also die JA-Schaltfläche angeklickt hat), führen Sie den Code in den Zeilen 10 bis 11 aus: Er setzt die Größe des Formulars auf die Größe des Benutzerbildschirms und seine Position
245
Mit Dialogfeldern arbeiten
dort auf die obere linke Ecke. Alternativ können Sie die Zeilen 10 bis 11 durch folgenden Code ersetzen, um die Anwendung zu maximieren: this.WindowState = FormWindowState.Maximized;
Weil Sie die MessageBox mit Hilfe des Schaltflächensatzes MessageBoxButtons.YesNo angezeigt haben, weiß die CLR, dass die JA-Schaltfläche den Wert DialogReturn.Yes und die NEIN-Schaltfläche DialogResult.No zurückgeben soll. Erinnern Sie sich an das Beispiel des Word-Dialogfensters »Möchten Sie die Änderungen in [Dokumentname] speichern?«. Klicken Sie auf JA, dann gibt das Dialogfeld Dialogresult.Yes zurück und Word weiß Bescheid: Es führt den Code zum Sichern des Dokuments aus (es öffnet das Dialogfeld DATEI/SPEICHERN UNTER nur, falls es sich um eine neues Dokument handelt) und schließt dann das Fenster. Klicken Sie hingegen auf NEIN, wird DialogResult.No übergeben, das Dokument wird nicht gespeichert und Word wird beendet (sofern Sie den BEENDEN-Befehl in irgendeiner Form erteilt haben). Klicken Sie auf ABBRECHEN, lautet das Ergebnis DialogResult.Cancel und nichts passiert (das Dokument wird weder gespeichert noch geschlossen, auch Word wird nicht beendet). Das Ergebnis eines Dialogfeldes auf diese Weise abzurufen ist eine hervorragende, standardisierte Möglichkeit, um Anwendungen über Benutzeraktionen zu informieren. Was passiert jedoch, wenn Sie Ihr eigenes Dialogfeld erstellt haben? Woher weiß die CLR, dass Ihre ÄNDERN!-Schaltfläche einen Wert DialogResult.Yes zurückgeben soll? Tatsächlich ist es viel einfacher als Sie befürchten. Schaltflächen und Formulare verfügen über eine DialogResult-Eigenschaft, mit der Sie angeben können, welches Ergebnis zurückgegeben werden soll. Schauen wir uns eine modifizierte Version des benutzerdefinierten Dialogfeldes aus Listing 7.1 an, die hier in Listing 7.4 in C#-Code zu sehen ist. Listing 7.4: Eine modifiziertes benutzerdefiniertes Dialogfeld in C# 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15:
246
using System; using System.Windows.Forms; using System.Drawing; namespace TYWinForms.Day7 { public class ChangeCaption2 : Form { private Button btnChange = new Button(); private TextBox tbCaption = new TextBox(); public String Caption { get { return tbCaption.Text; } }
Dialogfelder
16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30:
public ChangeCaption2() { tbCaption.Location = new Point(10,10); btnChange.Text = "Ändern!"; btnChange.Location = new Point(125,10); btnChange.DialogResult = DialogResult.OK; this.Text = "Überschrift ändern "; this.Size = new Size(250,100); this.Controls.Add(btnChange); this.Controls.Add(tbCaption); } } }
Von der Sprache (und der Namensänderung der Klasse in Zeile 7) einmal abgesehen, gibt es in diesem Listing nur zwei Unterschiede zum Listing 7.1. Der Ereignishandler für das Clicked-Ereignis ist jetzt verschwunden, ebenso die Zuweisung eines Delegaten zu der Schaltfläche. Das wird gleich erklärt. Der zweite Unterschied besteht im Hinzufügen der Zeile 22. Hier weisen Sie der btnChange-Schaltfläche den Wert DialogResult.OK zu. Dies bewirkt zweierlei. Erstens gibt es dem übergeordneten Formular den Wert DialogResult.OK zurück (so wie die MessageBox in Listing 7.3 DialogResult.Yes zurückgab). Wird zweitens diese Schaltfläche angeklickt, schließt die CLR das Formular automatisch für Sie. Daher brauchen Sie nicht mehr die Hide-Methode aufzurufen und demzufolge auch keinen Ereignishandler für das Clicked-Ereignis. Indem Sie also die DialogResult-Eigenschaft angegeben haben, weiß die CLR, dass diese Klasse ein Dialogfeld ist und sich entsprechend verhält. Die Hauptanwendung muss sich ebenfalls ein klein wenig ändern. Die ShowChangeBoxMethode aus Listing 7.2 muss wie im folgenden Codestück aussehen: public void ShowChangeBox(Object Sender, EventArgs e) { DialogResult objDR; ChangeCaption2 dlgChangeCaption = new ChangeCaption2(); objDR = dlgChangeCaption.ShowDialog(); if (objDR == DialogResult.OK) { this.Text = dlgChangeCaption.Caption; } this.Text = dlgChangeCaption.Caption; }
247
Mit Dialogfeldern arbeiten
(Vergessen Sie nicht, auch den Rest des Listings in C#-Code umzuwandeln.) Das Ergebnis des neuen Dialogfeldes wird in der objDR-Variablen gespeichert, die in der zweiten Zeile erstellt wurde. Dieser Wert wird dann mit DialogResult.OK verglichen, und falls sie gleichwertig sind, ändert sich die Titelzeile des Formulars. Ist das nicht der Fall, passiert nichts. Ich empfehle
Bitte beachten Sie
Weisen Sie den Schaltflächen in Ihren benutzerdefinierten Dialogfeldern DialogResult-Werte zu. Dies ist eine Standardkonvention und erleichtert Ihnen und denjenigen, die Ihre Klasse verwenden, die Verwendung Ihrer benutzerdefinierten Dialogfelder.
Verlassen Sie sich nicht auf das übergeordnete Formular, dass es Ihr Dialogergebnis interpretiert, selbst wenn Sie der Einzige sind, der es je zu sehen bekommt. Das Zuweisen von DialogResult-Werten sorgt dafür, dass Ihre Anwendung standardisierte Werte verwendet, die leicht auszuwerten sind.
7.4
Standarddialogfelder
Wie gesagt gibt es sieben Standarddialogfelder in .NET (vgl. Aufzählung oben). Sie sind bereits vorgefertigt, um das Ausführen der entsprechenden Aufgaben zu erleichtern. In den folgenden Abschnitten wollen wir etwas über ihren jeweiligen Funktionsumfang erfahren. (Nähere Informationen finden Sie in Anhang B.) Alle sieben Dialogfelder außer PrintPreviewDialog weisen folgende zwei Mitglieder auf: ShowHelp und Reset. Das erste gibt an, ob im Dialogfeld eine HilfeSchaltfläche angezeigt werden soll. Die Reset-Methode setzt alle Eigenschaften eines bestimmten Dialogs auf ihre Vorgabewerte zurück. Diese Methode erweist sich im Umgang mit diesen vielschichtigen Steuerelementen als besonders hilfreich.
Ein- und Ausgabe von Dateien Die zwei Standarddialogfelder für die Ein- und Ausgabe (E/A bzw. I/O) von Dateien sind die Steuerelemente OpenFileDialog und SaveFileDialog. Sie haben eine Reihe von Funktionen gemeinsam, weshalb die meisten ihrer Eigenschaften, die wir hier besprechen, in beiden zu finden sind. Natürlich ist ihre wichtigste Eigenschaft FileName (und FileNames), denn sie gibt an, welche Datei der Benutzer zum Öffnen oder Speichern ausgewählt hat. Ihr Hauptformular kann diesen Wert holen und die betreffende Datei nach Bedarf öffnen oder speichern (mehr dazu an Tag 11). Das Instantiieren und Anzeigen dieser Steuerelemente ist einfach:
248
Standarddialogfelder
dim dlgOpen as new SaveFileDialog dlgOpen.ShowDialog
Dieser Code ergibt das in Abbildung 7.6 dargestellte Dialogfeld.
Abbildung 7.6: Das Steuerelement für den DATEI/SPEICHERN-Dialog ist in den meisten WindowsAnwendungen zu finden.
Beide Dialogfelder geben entweder DialogResult.OK oder DialogResult.Cancel zurück, je nachdem, ob der Benutzer SPEICHERN bzw. ÖFFNEN oder ABBRECHEN angeklickt hat. Daraufhin lässt sich die FileName-Eigenschaft abfragen: if dlgOpen.ShowDialog = DialogResult.OK then Me.Text = dlgOpen.FileName end if
Im OpenFileDialog können Sie dem Benutzer erlauben, mehr als eine Datei auszuwählen, indem Sie MultiSelect auf true setzen. Die FileName-Eigenschaft nimmt dann ein Array von Zeichenfolgen auf, die diese Dateien repräsentieren. Zusätzlich zu diesen Haupteigenschaften (FileName bzw. FileNames) gibt es noch weitere, mit denen Sie die Funktionen dieser Dialogfelder anpassen können. Die Filter-Eigenschaft lässt sich dazu verwenden, die Dateitypen festzulegen, die in den Feldern SPEICHERN UNTER/DATEITYP und ÖFFNEN/DATEITYP zu sehen sind. Diese Filter begrenzen die Typen von Dateien, die der Benutzer speichern oder öffnen kann. Der Filter selbst ist eine speziell formatierte Zeichenfolge. Der folgende Code lässt nur das Öffnen von Word(.doc) und Text- (.txt) Dateien zu: dlgOpen.Filter ="Nur-Text-Dateien (*.txt)|*.txt|Word-Dokumente (*.doc)|*.doc "
Die Zeichenfolge lässt sich in folgende Bestandteile zerlegen: Beschreibung (Erweiterung) | Filterzeichenfolge
249
Mit Dialogfeldern arbeiten
Die Beschreibung ist der benutzerfreundliche Text, den der Benutzer im jeweiligen Dialogfeld angezeigt bekommt. Der senkrechte Strich (das Pipe-Symbol, | ) trennt in dieser Zeichenfolge die Felder. Die Filterzeichenfolge wird vom Dialogfeld dazu verwendet, Dateinamenendungen zu filtern; *.txt bedeutet, dass nur Dateien mit der .txt-Endung im Dialogfeld erscheinen werden. Nachfolgende Dateitypen lassen sich mit dem senkrechten Strich abtrennen. FilterIndex lässt sich verwenden, um den Standardfilter, der vorausgewählt ist, anzugeben. InitialDirectory ist eine Zeichenfolge, die das Anfangsverzeichnis angibt, in dem das
Dialogfeld mit der Anzeige beginnt. Eine Reihe von Eigenschaften fordern den Benutzer unter bestimmten Bedingungen zu Eingaben auf. Setzt man die CheckPathExists-Eigenschaft in beiden Steuerelementen auf true, dann gibt das Dialogfeld eine Warnung aus, falls der aktuell angegebene Pfad nicht existiert. Die CheckFileExists-Eigenschaft des Dialogfeldes OpenFileDialog erledigt die gleiche Aufgabe hinsichtlich einer bestimmten Datei. CreatePrompt und OverwritePrompt im SaveFileDialog ähneln einander; beim ersten wird der Benutzer gefragt, ob eine neue Datei angelegt werden soll, falls sie noch nicht existiert, und das zweite verlangt vom Benutzer eine Entscheidung, ob eine vorhandene Datei überschrieben werden soll.
Farben und Schriftarten wählen Das Steuerelement ColorDialog ist eines der am leichtesten zu erlernenden Dialog-Steuerelemente. Es besitzt nur wenige Eigenschaften, die Sie kennen müssen. Listing 7.5 zeigt ein Beispiel für den Gebrauch dieses Steuerelements beim Einstellen der Hintergrundfarbe für Ihr Formular. Listing 7.5: Die Verwendung des Steuerelements ColorDialog 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14:
250
using System; using System.Windows.Forms; using System.Drawing; namespace TYWinForms.Day7 { public class Listing75 : Form { public Button btColor = new Button(); public Listing75() { btColor.Text = "Farbe ändern!"; btColor.Location = new Point(100,100); btColor.Size = new Size(100,25); btColor.Click += new EventHandler(this.ChangeColor);
Standarddialogfelder
15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30:
this.Controls.Add(btColor); } public void ChangeColor(Object Sender, EventArgs e) { ColorDialog dlgColor = new ColorDialog(); if (dlgColor.ShowDialog() == DialogResult.OK) { this.BackColor = dlgColor.Color; } } public static void Main() { Application.Run(new Listing75()); } } }
Dieses Listing zeigt eine einfache Anwendung mit nur einer Schaltfläche, die das Standarddialogfeld für die Farbauswahl anzeigt. Wählt der Benutzer eine Farbe in ColorDialog aus, wird diese in der Eigenschaft ColorDialog.Color abgelegt. In Zeile 22 nehmen wir diese Farbe und weisen sie dem Wert BackColor des Hauptformulars zu. Abbildung 7.7 zeigt das Ergebnis dieser Anwendung und das Dialogfeld ColorDialog.
Abbildung 7.7: Das Steuerelement ColorDialog ist ein leicht zu bedienendes Dialogfeld.
251
Mit Dialogfeldern arbeiten
Das ist schon alles, was Sie für den Einsatz von ColorDialog benötigen. Das Steuerelement hat noch weitere Funktionen, mit denen der Benutzer mehr Farben als die dargestellten auswählen kann. Wenn Sie AllowFullOpen auf true setzen, kann der Benutzer ein zusätzliches Fenster öffnen (indem er auf die Schaltfläche FARBEN DEFINIEREN klickt, die sich am unteren Rand des Dialogfeldes in Abbildung 7.7 befindet), um eigene Farben zu definieren, die er aus allen Farben auswählt, die ihm das jeweilige Betriebssystem anbietet. (Wenn Sie diese Funktion nicht gestatten wollen, setzen Sie AllowFullOpen auf false und setzen auch die Eigenschaft FullOpen, die die Schaltfläche für benutzerdefinierte Farben anzeigt, auf false.) Nachdem der Benutzer einige selbst definierte Farben ausgewählt hat, können Sie die Eigenschaft CustomColors dazu verwenden, um ein Array von Color-Objekten zurückzugeben. Das Schriftartdialogfeld FontDialog ist ein wenig vielschichtiger, denn es gibt ja eine Vielzahl unterschiedlicher Schriftstile. Die Farbe Blau ist nun mal blau, aber die Schriftart Arial kann Arial fett, kursiv oder Standard sein, und als Größen sind beispielsweise 10, 20 oder 36 Punkt möglich. Und schließlich gibt es noch unterschiedliche Schriftstile wie etwa Skript (Nicht-OEM- und ANSI-Zeichensätze), Vektorschriften, vertikale Schriften und so weiter. Wenn Sie nichts von diesen Details halten, können Sie einfach die Eigenschaft FontDialog.Font verwenden, um die vom Benutzer ausgewählte Schrift zu zurückzugeben – das haben Sie sicherlich schon vermutet: if (dlgFont.DialogResult == DialogResult.OK) { this.Font = dlgFont.Font; }
Das ist wesentlich einfacher als jedes Mal, wenn man die Schriftart ändern möchte, ein neues Font-Objekt erstellen zu müssen! Abbildung 7.8 zeigt das Standarddialogfeld für die Schriftartauswahl.
Abbildung 7.8: Das Standarddialogfeld FontDialog bietet zahlreiche Auswahlmöglichkeiten an.
252
Standarddialogfelder
Alle Felder im Schriftartdialogfeld (Unterstreichen, Größe, Zeichensätze usw.) lassen sich anpassen. Mit AllowScriptChange kann der Benutzer den Standardzeichensatz ändern, während ScriptsOnly nur Skriptschriften anzeigt. AllowSimulations, AllowVectorFonts und AllowVerticalFonts gestatten die Verwendung der angegebenen Schriftarttypen (sie sind per Vorgabe alle auf true gesetzt). FixedPitchOnly erlaubt nur die Anzeige von Schriftarten mit festem Zeichenabstand (z.B. Courier). Mit ShowColor kann der Benutzer die Farbe seiner Schrift ändern (die ColorEigenschaft gibt diese Farbe zurück) und ShowEffects zeigt die Ergebnisse der Unterstrei-
chungsoptionen. Sie können schließlich noch die Eigenschaften MaxSize und MinSize einstellen, damit der Benutzer nur im Bereich zwischen einer maximalen und minimalen Schriftgröße auswählen kann. Die Einstellung dieser Werte auf null versetzt das System in die Lage, diese Werte zu steuern – es wird normalerweise die komplette Bandbreite der Größen erlauben.
Drucken Die letzte Reihe von Standarddialogfeldern, die wir betrachten, befasst sich mit dem Seitendruck: PageSetupDialog, PrintDialog und PrintPreviewDialog. Sie sollten allen vertraut sein, die schon einmal Dokumente in Windows gedruckt haben. PageSetupDialog gestattet die Randeinstellung für ein Dokument, das Ändern der Ausrichtung (Hoch- oder Querformat ), das Ändern des eingestellten Papierformats usw.
Bevor Sie jedoch einen PageSetupDialog-Dialogfeld verwenden können, müssen Sie Ihre Einstellungen auf etwas anwenden können. Dies wird durch das Erstellen eines PageSettings-Objekts bewerkstelligt, woraufhin es der PageSettings-Eigenschaft von PageSetupDialog zugewiesen wird. Sie gibt an, auf welche Objekte diese Einstellungen angewendet werden sollen. Listing 7.6 zeigt eine Beispielanwendung, die genau dies tut. Listing 7.6: Das Einrichten einer Seite 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11:
using using using using
System; System.Windows.Forms; System.Drawing; System.Drawing.Printing;
namespace TYWinForms.Day7 { public class Listing76 : Form { private PageSettings objPageSettings; private Button btShowDialog = new Button(); public Listing76() {
253
Mit Dialogfeldern arbeiten
12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32:
btShowDialog.Text = "Seiteneinstellungen"; btShowDialog.Click += new EventHandler(this.ShowSettings); this.Controls.Add(btShowDialog); } private void ShowSettings(Object Sender, EventArgs e) { if (objPageSettings == null) { objPageSettings = new PageSettings(); } PageSetupDialog dlgSettings = new PageSetupDialog(); dlgSettings.PageSettings = objPageSettings; dlgSettings.ShowDialog(); } public static void Main() { Application.Run(new Listing76()); } } }
Die Verwaltung von Seiteneigenschaften kann etwas kompliziert sein. Erstellen Sie hierzu zunächst ein Objekt System.Drawing.Printing.PageSettings (siehe Zeile 8; beachten Sie das neue using-Statement in Zeile 4). Dieses Objekt wird im Rest Ihrer Anwendung verwendet, daher ist es nötig, ein der ganzen Klasse zur Verfügung stehendes Objekt zu erstellen. In der ShowSettings-Methode in Zeile 18 müssen Sie dieses PageSettings-Objekt überprüfen. Ist es null (oder Nothing in VB .NET), sind diesem Dokument noch keine Eigenschaften zugewiesen worden, so dass Sie dieses Objekt (in Zeile 20) instantiieren müssen. Ist es aber nicht null, wurde das PageSettings-Objekt bereits instantiiert und es wurden Eigenschaften zugewiesen; daher wollen wir es wieder und wieder verwenden. Zeile 23 erzeugt einen neuen PageSetupDialog. In Zeile 24 wird das PageSettingsObjekt (ob neu oder bereits vorhanden) der PageSettings-Eigenschaft zugewiesen. Nun können die vom Benutzer gewählten Einstellungen gespeichert und nach Bedarf auf das Dokument angewendet werden. Die Methode ShowDialog wird in Zeile 25 aufgerufen; beachten Sie, dass Sie ihren DialogResult-Wert nicht prüfen müssen. Der Grund: Der PageSetupDialog kümmert sich selbst darum, die Einstellungen anzuwenden, je nachdem, welche Schaltfläche der Benutzer angeklickt hat. Uns kümmert es daher nicht, ob der Benutzer OK oder ABBRECHEN angeklickt hat, denn der PageSetupDialog handhabt dies intern.
254
Standarddialogfelder
Kompilieren Sie diese Anwendung und führen Sie sie aus. (Wie Sie noch von Tag 2 wissen, befindet sich der Namensraum System.Drawing.Printing in der Assembly System.drawing.dll, so dass Sie beim Kompilieren keine zusätzlichen Referenzen hinzufügen müssen.) Klicken Sie auf die Schaltfläche SEITE EINRICHTEN und ändern Sie einige Seiteneigenschaften. Schließen Sie den Dialog und öffnen ihn erneut mit der Schaltfläche. Beachten Sie, dass die vorgenommenen Änderungen sich immer noch im PageSetupDialog zeigen. Der Grund dafür ist, dass die vorgenommenen Änderungen dem PageSettingsObjekt zugewiesen wurden, welches immer wieder verwendet wird. Abbildung 7.9 zeigt das Ergebnis dieser Anwendung.
Abbildung 7.9: PageSetupDialog stellt ein Dialogfeld zur Seiteneinrichtung zur Verfügung.
Wie die anderen Standarddialogfelder, die wir heute besprochen haben, weist auch PageSetupDialog einige Eigenschaften auf, mit denen Sie das Dialogfeld anpassen können. AllowMargins, AllowOrientation, AllowPaper und AllowPrinter zeigen die Fenster an, in denen der Benutzer jeweils Ränder, Seitenausrichtung (hoch/quer), Papiergröße und Druckeinstellungen ändern kann. Wird die AllowPrinter-Einstellung geändert, benötigen Sie ein PrinterSettings-Objekt, doch dies werden wir an Tag 11 bezüglich Datei-E/A besprechen. MinMargins gibt den geringsten Seitenrand an, den Ihre Anwendung zulässt. Document gibt das PrintDocument-Objekt an, von dem man Seiteneinstellungen bezieht. Das PrintDialog-Steuerelement ist einfacher zu handhaben. Ähnlich wie der PrintSetupDialog enthält es die Eigenschaften Document und PrinterSettings, um die Einstellungen zu überwachen. Damit dieses Steuerelement funktioniert, müssen Sie der PrinterSettings-Eigenschaft ein PrinterSettings-Objekt zuweisen. Doch anders als im PageSetupDialog sollte man die DialogResult-Eigenschaft prüfen, sobald das Dialogfeld geöffnet wird. Abbildung 7.10 zeigt ein typisches DRUCKEN-Dialogfeld.
255
Mit Dialogfeldern arbeiten
Abbildung 7.10: Das Steuerelement PrintDialog stellt ein nützliches Dialogfeld für das Drucken zur Verfügung.
Das letzte Standarddialogfeld, das wir heute erörtern, ist das PrintPreviewDialog-Objekt. Es unterscheidet sich von den anderen Steuerelementen, denn anders als die anderen Standarddialogfelder erbt es nicht von der CommonDialog-Klasse. Es sieht nicht einmal wie ein Standarddialogfeld aus. Auch sein DialogResult muss nicht überwacht werden. Wir brauchen uns beim PrintPreviewDialog-Steuerelement nur um drei Eigenschaften zu kümmern. Document, die erste, gibt das Dokument für die Vorschau an. Doch Vorsicht: Diese Eigenschaft akzeptiert nicht jedes x-beliebige Dokument; sie ist sehr wählerisch. Sie müssen ihr ein PrintDocument-Objekt zuweisen, welches wiederum das Dokument repräsentiert, dessen Vorschau man sehen will. Wir werden dieses Objekt näher an Tag 11 besprechen. Die nächsten beiden Eigenschaften sind einfach. PrintPreviewControl gibt natürlich das Steuerelement PrintPreviewControl im PrintPreviewDialog-Fenster zurück. Dieses Steuerelement zeigt die eigentliche Vorschau an, denn der Rest der Schaltflächen und Steuerelemente von PrintPreviewDialog besteht aus separaten Elementen. Wenn Sie Ihr eigenes benutzerdefiniertes Druckvorschau-Dialogfeld erstellen wollen, werden Sie höchstwahrscheinlich das Steuerelement PrintPreviewDialog dafür verwenden. UseAntiAlias gibt an, ob das Vorschau-Dokument mit Antialiasing behandelt wird, das
heißt, ob die Kanten des Dokuments (genauer: der verwendeten Schriftzeichen) geglättet werden sollen, so dass keine Treppchen oder harten Kanten auftauchen. Das Antialiasing lässt die Darstellung weicher und angenehmer aussehen, natürlich auf Kosten der Schärfe. Abbildung 7.11 zeigt das Steuerelement PrintPreviewDialog in Aktion.
256
Zusammenfassung
Abbildung 7.11: Sie können das Dialogfenster PrintPreviewDialog
dazu verwenden, eine Seitenvorschau eines Dokuments zu erhalten.
7.5
Zusammenfassung
Wie Sie heute erfahren haben, können Dialogfelder weitaus vielschichtiger und funktionsreicher sein als die einfache MessageBox, die Sie bislang verwendet haben. Dialogfelder werden für das Beschaffen von Benutzereingaben eingesetzt, ob das nun ein einfaches Ja oder Nein ist oder eine Auswahl unter Schriftarten und Farben. Zunächst muss man sich merken, dass Dialogfelder modal oder nichtmodal sein können. Ein modaler Dialog hindert den Benutzer daran, zur Hauptanwendung umzuschalten, es sei denn, er antwortet zuerst auf das Dialogfeld. Nichtmodale Dialoge hingegen erlauben das Wechseln zwischen Dialogfeld und Hauptanwendung, doch können sie zu Problemen führen, da man mehr als ein Formular auf einmal steuern muss. Verwenden Sie daher nichtmodale Dialogfelder nur, wenn Sie dem Benutzer lediglich Informationen (z.B. im Hilfe-Fenster) anzeigen müssen, ohne zugleich Informationen einzuholen. Das Ihnen vertrauteste Dialogfeld ist die MessageBox (Meldungsfeld). Dieses Steuerelement besitzt nur eine Methode namens Show, die bis zu sieben Parameter übernehmen kann, um z.B. festzulegen, welches Symbol im Meldungsfeld erscheint, welche Schaltflächen verfügbar sind sowie die Titelzeile und der Text, die anzuzeigen sind. Wenn Sie mehr Funktionen benötigen, als die einfache MessageBox bereitstellt, müssen Sie Ihre eigenen Dialogfeldklassen erstellen. Man erstellt diese Klassen genau wie StandardForm-Klassen; man erbt von der Klasse System.Windows.Forms.Form und stellt Konstruktoren
257
Mit Dialogfeldern arbeiten
und Ereignishandler bereit. Sie können Werte Ihres benutzerdefinierten Dialogfeldes gegenüber dem Hauptformular offen legen, indem Sie (mit Hilfe der Get- und Set-Statements) public-Eigenschaften erzeugen. Ihre Dialogfelder können Werte zurückgeben, die angeben, auf welche Schaltfläche (wie etwa OK oder ABBRECHEN) der Benutzer zum Schließen des Dialogfeldes geklickt hat. Diese Werte befinden sich in der DialogResult-Aufzählung. Das übergeordnete Formular kann auf diese DialogResult-Werte hin prüfen, um herauszufinden, was der Benutzer mit dem Dialogfeld getan hat. Sie haben auch von der Vielzahl der Standarddialogfeld-Steuerelemente erfahren, die .NET bereitstellt. Diese Steuerelemente kapseln gebräuchliche Windows-Funktionen wie etwa die Auswahl von Schriftarten oder Farben, das Drucken und die Druckvorschau, um nur einige zu nennen. Die Themen der nächsten Woche befassen sich mit der Erhöhung des Funktionsumfangs Ihrer Anwendung, so etwa dem Hinzufügen einer Datenbank und von Grafikfähigkeiten. Vergessen Sie nicht das Bonusprojekt »Eine Textverarbeitung erstellen« am Ende dieser Woche anzusehen, bevor Sie fortfahren.
7.6 F
Fragen und Antworten
Worin besteht der Unterschied zwischen den heute erstellten Steuerelementen und öffentlichen Variablen? A
Mit beiden können Sie Daten von einer Klasse an eine andere übergeben. In Listing 7.1 hätten wir beispielsweise die Eigenschaft mit der folgenden Codezeile deklarieren können: public property Caption as String
Und solange Sie die Eigenschaft tbCaption.Text dieser Variable zuweisen, wäre die Funktionalität die gleiche. Der Unterschied in der Verwendung von Eigenschaft und öffentlichen Variablen besteht darin, dass Sie mit der Eigenschaft zusätzlichen Code in den get- und set-Statements ausführen können. Das erweist sich als nützlich, wenn Sie etwa Werte verifizieren müssen, bevor Sie sie zurückgeben: public property Caption as String Get if tbCaption.Text "" then return tbCaption.Text else return "Hier befindet sich nichts!"
258
Workshop
end if End Get End Property
Mit einer öffentlichen Variablen lässt sich keine zusätzliche Funktion ausführen. F
Wie bringe ich das Dokument dazu, in meinem Steuerelement PrintPreviewDialog zu erscheinen? A
7.7
Die Antwort erfordert Wissen über eine neue Klasse namens PrintDocument und neue Fertigkeiten, die Sie noch nicht erworben haben. Um nähere Informationen hierzu zu erhalten, lesen Sie bitte Tag 11.
Workshop
Dieser Workshop soll Ihnen helfen, sich besser an die heute behandelten Begriffe zu erinnern. Es hilft Ihnen sehr, die Antworten vollständig zu verstehen, bevor Sie weiterlesen. Die Antworten zu den Quizfragen und den Übungen finden Sie in Anhang A.
Quiz 1. Wahr oder falsch? Jedes Form-Objekt kann ein Dialogfeld sein. 2. Wie macht man ein Dialogfeld modal bzw. nichtmodal? 3. Wahr oder falsch? Sie können ein MessageBox-Objekt direkt instantiieren. 4. Wie heißen die sieben Parameter, die die MessageBox.Show-Methode übernehmen kann? (Führen Sie nur ihre Typen mit einer kurzen Beschreibung auf.) 5. Welche zwei Vorteile erzielen Sie, wenn Sie in Ihrer benutzerdefinierten Dialogklasse einen DialogResult-Wert zuweisen? 6. Ist das folgende Codestück korrekt? Falls nicht, nennen Sie die Fehler. public property IsClicked as string Get return blnClicked end property
7. Welche zwei Mitglieder haben fast alle Standarddialogfeld-Steuerelemente gemeinsam? Nennen Sie die Ausnahme(n). 8. Schreiben Sie eine Filterzeichenfolge für ein OpenFileDialog-Steuerelement, das Textdateien (*.txt), Bilddateien (*.gif) und Alle Dateien (*.*)anzeigt.
259
Mit Dialogfeldern arbeiten
9. Nennen Sie die Haupteigenschaften (also die Eigenschaften, die Sie am meisten interessieren, wenn der Benutzer eine Auswahl trifft) für die Steuerelemente OpenFileDialog, SaveFileDialog, ColorDialog und FontDialog.
Übung Erstellen Sie in C# eine voll funktionsfähige Anwendung mit Menüs, welche alle Standarddialogfeld-Steuerelemente verwendet. Setzen Sie auch ein nichtmodales Dialogfeld ein, um kontextabhängige Hilfeinformationen anzuzeigen. Wenn sich ein Benutzer von einem Menüelement zum nächsten bewegt, sollte sich der Inhalt dieses Dialogfeldes ändern. (Tipp: Verwenden Sie das MenuItem.Select-Ereignis, um festzustellen, über welchem Menüelement sich die Maus gerade befindet.)
260
Woche 1 – Rückblick Projekt 1: Eine Textverarbeitung erstellen Herzlichen Glückwunsch zum Überstehen der ersten Woche! Sie dürften nun mit dem Schreiben von Windows Forms-Anwendungen in C# und Visual Basic .NET vertraut sein. Ihnen dürften auch die Schwierigkeiten beim Umgang mit Ereignissen und Windows Forms-Steuerelementen bewusst sein. Mit dem bislang erworbenen Wissen sind Sie bereit, praktisch jede einfache Anwendung anzupacken, aber auch einige der vielschichtigeren. Als eine Form der Rekapitulation der vergangenen sieben Tage wird dieses Rückblickkapitel (sowie diejenigen der Wochen 2 und 3) Sie durch den Vorgang der Erstellung Ihres eigenen Textverarbeitungsprogramms führen, und zwar eines Programms, das es mit Microsoft Word aufnehmen könnte. In dieser Lektion legen Sie erst einmal das Fundament für diese Anwendung; das Bonusprojekt am Ende der zweiten Woche wird Ein- und Ausgabefunktionen für Daten und Dateien hinzufügen. Das Projekt 3 wird einige fortgeschrittenere Leistungsmerkmale integrieren. Es wird empfohlen, dass Sie diese Projekte durcharbeiten. Sie machen Sie besser mit Windows Forms-Konzepten vertraut, da Sie Situationen aus der Praxis handhaben werden.
Über das Textverarbeitungsprojekt Dieses Projekt soll Ihnen Gelegenheit geben, an einer Anwendung in jeder Phase ihrer Erstellung zu arbeiten. In dieser Lektion fangen Sie erst einmal mit einem einfachen Entwurf an, installieren die grundlegenden Funktionen und erweitern diese um peppige Zusatzelemente. Dieses Projekt dient auch dazu, Ihre Fähigkeiten zu stählen, was die Entwicklung von Windows Forms-Anwendungen anbelangt. So können Sie sich später in Ihren anderen Projekten sicherer fühlen. Am Ende der zweiten bzw. dritten Woche werden Sie zu diesem Projekt zurückkehren, um die Ergebnisse der weiteren Lektionen zu integrieren. Diese Anwendung, die wir NetWord taufen wollen, wird es dem Benutzer gestatten, Dokumente zu bearbeiten und zu speichern, Suchoperationen aus zuführen, Text zu formatieren und schließlich Dokumente zu drucken und im Web zu surfen. In dieser Lektion jedoch werden wir lediglich das grundlegende Gerüst sowie die Benutzeroberfläche erstellen.
261
Woche 1 – Rückblick
Was Sie benötigen Der wichtigste Teil jedes Textverarbeitungsprogramms ist natürlich die Hauptbedienoberfläche: der Textbereich, in dem der Benutzer Dokumente tippen und bearbeiten kann. Dreimal dürfen Sie raten, wie wir diese Komponente erstellen. Ich hoffe, Sie haben auf das Steuerelement RichTextBox getippt. Bereits an Tag 6 haben wir gesehen, dass dieses Steuerelement beinahe alle Funktionen eines Textverarbeitungsprogramms ausführen kann, vom Formatieren eines Textes bis zur Durchführung einer Suchoperation. Das Einrichten des RichTextBox-Steuerelements bedeutet jedoch nur den halben Sieg. Man muss auch Menüs hinzufügen, mit denen der Benutzer zum Beispiel Dateien öffnen und speichern, Text formatieren und Farben auswählen kann. Die Menüs sowie das RichTextBox-Steuerelement stellen in diesem Projekt die Hauptteile der Anwendung dar. Um die Anwendung noch bedienungsfreundlicher zu machen, sind noch weitere Steuerelemente nötig, so etwa eine Statusleiste und Kontextmenüs. Sie werden im Abschnitt »Einige nützliche Dinge mit Pfiff hinzufügen« weiter unten beschrieben. Obwohl die meisten Textverarbeitungsprogramme MDI-Anwendungen (Multiple Document Interface, Mehrfachdokumentschnittstelle) sind – sie können mehr als ein Dokument zugleich öffnen –, wird unsere erst einmal nur ein einzelnes Dokument anzeigen können. Auf MDI-Anwendungen kommen wir an Tag 10 zurück.
Die Benutzeroberfläche aufbauen Listing R1.1 zeigt das grundlegende Gerüst für unsere Anwendung. Listing R1.1: Die NetWord-Klasse 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12:
262
using System; using System.Windows.Forms; using System.Drawing; namespace TYWinforms.P1 { public class NetWord : Form { public NetWord() { this.Text = "NetWord v.1"; this.Size = new Size(800,600); } public static void Main() {
Woche 1 – Rückblick
13: 14: 15: 16:
Application.Run(new NetWord()); } } }
Speichern Sie dieses Listing unter dem Namen NetWord.cs. Wie Sie sehen, weist der Code außer einer Main-Methode und einem einfachen Konstruktor kaum etwas Bemerkenswertes auf. Das Kompilieren und Ausführen dieser Anwendung wird zu einer leeren Applikation wie in Abbildung R1.1 führen. Wir wollen als Nächstes die Benutzeroberfläche erstellen. Wir müssen das RichTextControl-Steuerelement hinzufügen und alle anderen Steuerelemente, die damit verknüpft sind (wie etwa Menüs). Werfen Sie einen Blick auf Listing R1.2.
Abbildung R1.1: Die bescheidenen Anfänge von NetWord
Listing R1.2: Die Document-Klasse 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12:
using using using using using
System; System.Windows.Forms; System.Drawing; System.Drawing.Printing; System.ComponentModel;
namespace TYWinforms.P1 { public class NetWord : Form { private RichTextBox rtbDocument = new RichTextBox(); private MainMenu mnuMain = new MainMenu(); private MenuItem mniFile = new MenuItem("Datei");
263
Woche 1 – Rückblick
13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58:
264
private private private private private private
MenuItem MenuItem MenuItem MenuItem MenuItem MenuItem
mniOpen = new MenuItem("Öffnen..."); mniSave = new MenuItem("Speichern"); mniPageSetup = new MenuItem("Seite einrichten..."); mniPrintPreview = new MenuItem ("Druckvorschau"); mniPrint = new MenuItem("Drucken..."); mniExit = new MenuItem("Beenden");
private private private private private private
MenuItem MenuItem MenuItem MenuItem MenuItem MenuItem
mniEdit = new MenuItem("Bearbeiten"); mniUndo = new MenuItem("Rückgängig"); mniCut = new MenuItem("Ausschneiden"); mniCopy = new MenuItem("Kopieren"); mniPaste = new MenuItem("Einfügen"); mniFind = new MenuItem("Suchen...");
private MenuItem mniFormat = new MenuItem("Format"); private MenuItem mniFont = new MenuItem("Schriftart..."); private private private private private private private private private private
Font fntCurrent = new Font("Times New Roman", 10); Color fntColor = Color.Black; FontDialog dlgFont = new FontDialog(); OpenFileDialog dlgOpen = new OpenFileDialog(); SaveFileDialog dlgSave = new SaveFileDialog(); PrintPreviewDialog dlgPrintPreview = new PrintPreviewDialog(); PageSetupDialog dlgPageSetup = new PageSetupDialog(); PrintDialog dlgPrint = new PrintDialog(); PageSettings objPageSettings = new PageSettings(); PrinterSettings objPrintSettings = new PrinterSettings();
public NetWord() { rtbDocument.Dock = DockStyle.Fill; rtbDocument.Font = fntCurrent; rtbDocument.HideSelection = false; mniOpen.Click += new EventHandler(this.FileClicked); mniSave.Click += new EventHandler(this.FileClicked); mniPageSetup.Click += new EventHandler (this.FileClicked); mniPrintPreview.Click += new EventHandler (this.FileClicked); mniPrint.Click += new EventHandler(this.FileClicked); mniExit.Click += new EventHandler(this.FileClicked); mniUndo.Click += new EventHandler(this.EditClicked); mniCut.Click += new EventHandler(this.EditClicked); mniCopy.Click += new EventHandler(this.EditClicked); mniPaste.Click += new EventHandler(this.EditClicked); mniFind.Click += new EventHandler(this.EditClicked);
Woche 1 – Rückblick
59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80: 81: 82: 83: 84: 85: 86: 87: 88: 89: 90: 91:
mniFont.Click += new EventHandler (this.FormatClicked); mnuMain.MenuItems.Add(mniFile); mnuMain.MenuItems.Add(mniEdit); mnuMain.MenuItems.Add(mniFormat); mniFile.MenuItems.Add(mniOpen); mniFile.MenuItems.Add(mniSave); mniFile.MenuItems.Add("-"); mniFile.MenuItems.Add(mniPageSetup); mniFile.MenuItems.Add(mniPrintPreview); mniFile.MenuItems.Add(mniPrint); mniFile.MenuItems.Add("-"); mniFile.MenuItems.Add(mniExit); mniEdit.MenuItems.Add(mniUndo); mniEdit.MenuItems.Add("-"); mniEdit.MenuItems.Add(mniCut); mniEdit.MenuItems.Add(mniCopy); mniEdit.MenuItems.Add(mniPaste); mniEdit.MenuItems.Add("-"); mniEdit.MenuItems.Add(mniFind); mniFormat.MenuItems.Add(mniFont); this.Text = "NetWord v.1"; this.Name = "NetWord"; this.Size = new Size(800,600); this.Menu = mnuMain; this.Closing += new CancelEventHandler(this.DocumentClosing); this.Controls.Add(rtbDocument); }
Das gesamte Listing besteht lediglich aus dem Konstruktor und der Initialisierung unserer Anwendung. Inzwischen wissen Sie, dass der Großteil des von uns geschriebenen Codes (zumindest in der ersten Woche) aus der Initialisierung der Benutzeroberfläche besteht. Diesem Code kann man sehr leicht folgen. Zeile 9 erzeugt das RichTextBoxSteuerelement, das für die Hauptbenutzeroberfläche benötigt wird. Die Zeilen 11 bis 28 richten die diversen Menüs ein und in Zeile 30 bis 39 werden Einstellungen deklariert, die durchweg verwendet werden (die aktuelle Schriftart, Farbe, Druckeinstellung usw.). Die Zeilen 42 bis 90 richten einige allgemeine Eigenschaften ein und weisen den Menüelementen Delegaten zu.
265
Woche 1 – Rückblick
Es gibt hierzu noch einiges anzumerken. Erstens setzt Zeile 44 HideSelection auf false, was dazu führt, dass markierter Text im RichTextBox-Steuerelement auch dann noch als markiert zu sehen ist, wenn dieses Steuerelement nicht den Fokus hat (das wird bei der Erstellung der Suchfunktion wichtig). Zweitens weist Zeile 88 dem Closing-Ereignis des Formulars einen Delegaten zu. Wir wollen dieses Ereignis überwachen, um herauszufinden, ob sich der Inhalt vor dem Schließen des Fensters geändert hat; falls dem so ist, wollen wir dem Benutzer Gelegenheit zum Speichern seines Dokumentes geben. Wir wollen nun einen Blick auf die Ereignishandler werfen, die in Listing R1.3 zu sehen sind. Listing R1.3: Die Ereignishandler für NetWord 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31:
266
private void FileClicked(Object Sender, EventArgs e) { MenuItem mniTemp = (MenuItem)Sender; switch (mniTemp.Text) { case "Öffnen...": if (dlgOpen.ShowDialog() == DialogResult.OK) { //Datei öffnen } break; case "Speichern": if (dlgSave.ShowDialog() == DialogResult.OK) { //Datei speichern } break; case "Seite einrichten...": dlgPageSetup.PageSettings = objPageSettings; dlgPageSetup.ShowDialog(); break; case "Druckvorschau": dlgPrintPreview.Document = new PrintDocument(); dlgPrintPreview.ShowDialog(); break; case "Drucken...": dlgPrint.PrinterSettings = new PrinterSettings(); if (dlgPrint.ShowDialog() == DialogResult.OK) { //drucken
Woche 1 – Rückblick
32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77:
} break; case "Beenden": this.Close(); break; } } private void EditClicked(Object Sender, EventArgs e) { MenuItem mniTemp = (MenuItem)Sender; switch (mniTemp.Text) { case "Rückgängig": rtbDocument.Undo(); break; case "Ausschneiden": if (rtbDocument.SelectedRtf != "") { rtbDocument.Cut(); } break; case "Kopieren": if (rtbDocument.SelectedRtf != "") { rtbDocument.Copy(); } break; case "Einfügen": rtbDocument.Paste(); break; case "Suchen...": FindDialog dlgFind = new FindDialog(rtbDocument); dlgFind.Show(); break; } } private void FormatClicked(Object Sender, EventArgs e) { MenuItem mniTemp = (MenuItem)Sender; switch (mniTemp.Text) { case "Schriftart...": dlgFont.ShowColor = true; if (dlgFont.ShowDialog() == DialogResult.OK) { fntCurrent = dlgFont.Font; fntColor = dlgFont.Color; rtbDocument.SelectionFont = fntCurrent; rtbDocument.SelectionColor = fntColor;
267
Woche 1 – Rückblick
78: } 79: break; 80: } 81: } 82: 83: private void DocumentClosing(Object Sender, CancelEventArgs e) { 84: if (rtbDocument.Modified) { 85: DialogResult dr = MessageBox.Show("Möchten Sie die Änderungen speichern?", this.Name, MessageBoxButtons.YesNoCancel); 86: 87: if (dr == DialogResult.Yes) { 88: MessageBox.Show("Schade!", this.Name); 89: } else if (dr == DialogResult.Cancel) { 90: e.Cancel = true; 91: } 92: } 93: } 94: 95: public static void Main() { 96: Application.Run(new NetWord()); 97: } 98: } 99: }
Nach unseren Arbeiten an Tag 4 dürfte Ihnen ein Großteil dieses Codes bekannt vorkommen. Sie finden in Zeile 1 die Methode, die alle MenuItems im DATEI-Menü behandelt, den Ereignishandler für das BEARBEITEN-Menü in Zeile 40, denjenigen für das FORMAT-Menü in Zeile 67 und den für das Closing-Ereignis schließlich in Zeile 83. Der Löwenanteil dieses Codes wird verwendet, um genau auszuwerten, welches Menüelement angeklickt wurde. Die Zeilen 4 bis 36 dürften vertraut erscheinen: Sie sind fast identisch mit der Dialogfeld-Lektion an Tag 7. Der einzige Unterschied: Es wurde ein BEENDENMenü hinzugefügt, das die Close-Methode aufruft. Je nachdem, welches Menü angeklickt wurde, erzeugen Sie einen FileOpenDialog, einen FileSaveDialog, einen PageSetupDialog, einen PrintPreviewDialog oder einen PrintDialog. Mit Hilfe der ShowDialog-Methode zeigen Sie das jeweilige Dialogfeld an. Bei den ÖFFNEN-, SPEICHERN- und DRUCKEN-Menüelementen bewerten Sie das Ergebnis des Dialogfelds mit Hilfe der DialogResult-Aufzählung und führen eine dementsprechende Aktion aus. Die Zeilen 43 bis 63 sind sehr ähnlich und führen die Arbeitsschritte RÜCKGÄNGIG, AUSSCHNEIDEN, KOPIEREN und EINFÜGEN aus, indem sie die verschiedenen Methoden des RichTextBox-Steuerelements aufrufen. Bei den Cutund Copy-Methoden müssen Sie zunächst prüfen, ob Text markiert ist, indem
268
Woche 1 – Rückblick
Sie die Eigenschaft SelectedRtf bewerten. Das SUCHEN-Menüelement in Zeile 60 ist etwas Besonderes; es erzeugt ein benutzerdefiniertes Dialogfeld, das Sie im nächsten Abschnitt erstellen werden, und zeigt es an. Die Zeilen 70 bis 78 verhalten sich wie die anderen Fälle. Man zeigt dem Benutzer das Dialogfeld FontDialog an, um ihm die Auswahl einer Schriftart und einer Schriftfarbe zu gestatten. Diese Werte werden in den globalen Einstellungen gespeichert, die Sie in den Zeilen 30 und 31 erzeugt haben, und dann der Schriftart im RichTextBox-Steuerelement zugewiesen. Schließlich findet sich in Zeile 83 noch die DocumentClosing-Methode. Sie erinnern sich, dass das Closing-Ereignis direkt vor dem Schließen des Dokuments stattfindet, was bedeutet, dass man es zur Bewertung aller in letzter Minute geänderten Bedingungen einsetzen kann. Die Zeile 84 prüft die Modified-Eigenschaft der RichTextBox, die Ihnen verrät, ob das Dokument seit dem letzten Speichervorgang (oder in diesem Fall, seit dem letzten Öffnen) geändert wurde. Zeile 85 zeigt eine MessageBox an, die den Benutzer fragt, ob er ohne Speichern der letzten Änderungen fortfahren möchte. Je nach der Antwort tun Sie eines von drei Dingen: 왘
Sie speichern das Dokument nicht.
왘
Sie halten die Anwendung vom Beenden ab (indem Sie die Eigenschaft CancelEventArgs.Cancel auf true setzen).
왘
Sie speichern das Dokument (momentan nicht wirklich, aber wenigstens wissen Sie, wohin der entsprechende Code gehört).
Das ist bereits alles – wenn auch nicht für die Anwendung. Wenn Sie versuchen, diesen Code auszuführen, erhalten Sie eine Fehlermeldung, denn Sie haben bis jetzt noch nicht das Objekt FindDialog erstellt, das von Zeile 61 gefordert wird. Diese Klasse ist in Listing R1.4 zu sehen. Listing R1.4: Die FindDialog-Klasse 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11:
using System; using System.Windows.Forms; using System.Drawing; namespace TYWinforms.P1 { public class FindDialog : Form { private Label lblFind = new Label(); private Label lblReplace = new Label(); private TextBox tbFind = new TextBox(); private TextBox tbReplace = new TextBox(); private Button btFind = new Button();
269
Woche 1 – Rückblick
12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57:
270
private Button btReplace = new Button(); private Button btReplaceAll = new Button(); private Button btCancel = new Button(); private RichTextBox rtbDoc; public FindDialog(RichTextBox rtbParentDoc): base() { rtbDoc = rtbParentDoc; lblFind.Text = "Suche: "; lblFind.Location = new Point(10,10); lblFind.Width = 50; lblReplace.Text = "Ersetze: "; lblReplace.Location = new Point(10,35); lblReplace.Width = 50; tbFind.Width = 100; tbFind.Location = new Point(60,10); tbReplace.Width = 100; tbReplace.Location = new Point(60,35); btFind.Size = new Size(75,25); btFind.Text = "Suchen"; btFind.Location = new Point(170,10); btFind.Click += new EventHandler(this.ButtonClicked); btReplace.Size = new Size(75,25); btReplace.Text = "Ersetzen"; btReplace.Location = new Point(170,35); btReplace.Click += new EventHandler(this.ButtonClicked); btReplaceAll.Size = new Size(75,25); btReplaceAll.Text = "Alle ersetzen"; btReplaceAll.Location = new Point(170,60); btReplaceAll.Click += new EventHandler(this.ButtonClicked); btCancel.Size = new Size(75,25); btCancel.Text = "Abbrechen"; btCancel.Location = new Point(170,85); btCancel.Click += new EventHandler(this.ButtonClicked); this.Text = "Suchen"; this.Size = new Size(255,140); this.MaximizeBox = false;
Woche 1 – Rückblick
58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80: 81: 82: 83: 84: 85: 86: 87: 88: 89: 90: 91: 92: 93: 94: 95: 96: 97: 98: 99: 100: 101:
this.MinimizeBox = false; this.FormBorderStyle = FormBorderStyle.FixedDialog; this.TopMost = true; this.Controls.Add(lblFind); this.Controls.Add(lblReplace); this.Controls.Add(tbFind); this.Controls.Add(tbReplace); this.Controls.Add(btFind); this.Controls.Add(btReplace); this.Controls.Add(btReplaceAll); this.Controls.Add(btCancel); } private void ButtonClicked(Object Sender, EventArgs e) { Button btTemp = (Button)Sender; int intLocation; int intCount = 0; switch (btTemp.Text) { case "Suchen": intLocation = rtbDoc.Find(tbFind.Text, rtbDoc.SelectionStart + rtbDoc.SelectionLength, RichTextBoxFinds.None); if (intLocation != -1) { rtbDoc.SelectionStart = intLocation; rtbDoc.SelectionLength = tbFind.Text.Length; rtbDoc.Focus(); } break; case "Ersetzen": intLocation = rtbDoc.Find(tbFind.Text, rtbDoc.SelectionStart + rtbDoc.SelectionLength, RichTextBoxFinds.None); if (intLocation != -1) { rtbDoc.SelectionStart = intLocation; rtbDoc.SelectionLength = tbFind.Text.Length; rtbDoc.SelectedText = tbReplace.Text; rtbDoc.SelectionStart = intLocation; rtbDoc.SelectionLength = tbReplace.Text.Length; } break; case "Alle ersetzen": intLocation = rtbDoc.Find(tbFind.Text); while (intLocation != -1) { rtbDoc.SelectionStart = intLocation; rtbDoc.SelectionLength = tbFind.Text.Length; rtbDoc.SelectedText = tbReplace.Text;
271
Woche 1 – Rückblick
102: 103:
intCount += 1; intLocation = rtbDoc.Find(tbFind.Text, rtbDoc.SelectionStart + rtbDoc.SelectionLength, RichTextBoxFinds.None); } MessageBox.Show(intCount.ToString() + "Vorkommen ersetzt","Suchen"); break; case "Abbrechen": this.Close(); break; } }
104: 105: 106: 107: 108: 109: 110: 111: 112: 113:
} }
Dieses Listing definiert ein benutzerdefiniertes Dialogfeld, das über SUCHENund ERSETZEN-Textfelder sowie Schaltflächen für SUCHEN, ERSETZEN und ALLE ERSETZEN verfügt. Der Code dafür mag ein wenig komplizierter aussehen als in den bisherigen Arbeitsschritten, doch ist er nicht wirklich schwierig. Die Zeilen 1 bis 70 enthalten den Konstruktor- und Initialisierungscode. Hier gibt es nichts Neues, lediglich ein paar Dinge, die zu beachten wären. Erstens erfolgt die Deklaration des RichTextBox-Steuerelements in Zeile 16; diesem Steuerelement wird dann dasjenige Steuerelement zugewiesen, das an den Konstruktor in Zeile 18 übergeben wurde. Wenn wir in Listing P 1.3 zur Zeile 61 zurückgehen, sehen wir, dass wir an den Konstruktor der FindDialog-Klasse das Haupt-RichTextBox-Steuerelement übergeben. Dies wird so gehandhabt, damit Sie beim Suchen von Ihrem SUCHEN-Dialogfeld aus eine einfache Referenz auf dieses Steuerelement haben. Wie man es einsetzt, sehen Sie gleich. Als Nächstes finden Sie im Konstruktor in Zeile 18 einen Aufruf der baseMethode. Das bedeutet, dass der Konstruktor in Zeile 18 vom Konstruktor des RichTextBox-Steuerelements erben sollte und dass alle notwendigen Initialisierungsprozeduren ausgeführt werden. Da .NET dies für Sie tut, ist es im Grunde nicht nötig, dies hier einzufügen, aber es macht es für Sie deutlicher. ButtonClicked in Zeile 72 verwendet ein switch-Statement, um die angeklickte Schaltfläche zu bewerten. In Zeile 79 stoßen Sie auf die Find-Methode, die, wie Sie wissen, bestimmten Text in der RichTextBox sucht. Doch sie weist ein paar zusätzliche Parameter auf: tbFind.Text ist der zu suchende Text, rtbDoc.SelectionStart bezeichnet die Startposition für den Suchvorgang im Text (also die aktuelle Cursorposition in der RichTextBox) und RichTextBoxFinds.None gibt an, wie die Suche durchgeführt werden soll. Die Aufzählung RichTextBoxFinds enthält die Werte MatchCase (Groß-/Kleinschreibung beachten), NoHighlight
272
Woche 1 – Rückblick
(keine Markierung), None, Reverse (umgekehrt) und WholeWord (nach ganzem Wort suchen). Diese Werte lassen sich wie Anchor-Werte kombinieren: RichTextBoxFinds.MatchCase | RichTextBoxFinds.Reverse
Mit Hilfe von RichTextBoxFinds kann der Benutzer spezialisierte Suchvorgänge starten, aber im Augenblick brauchen wir uns nicht darum zu kümmern. Falls die Find-Methode den gesuchten Text nicht findet, gibt sie den Wert –1 zurück. Wir prüfen dies in Zeile 80 und falls Text gefunden wurde, markieren wir ihn mit Hilfe der Eigenschaften SelectionStart und SelectionLength. Am Schluss geben Sie der RichTextBox den Fokus zurück. (Doch weil wir in Zeile 60 für die FindDialog-Klasse TopMost auf true gesetzt haben, wird das Dialogfeld nicht verschwinden, aber die Benutzereingaben gelangen trotzdem zum RichTextBox-Steuerelement.) Bei den case-Zweigen für Suchen und Ersetzen startet die Suche hinter der aktuellen Cursorposition (SelectionStart + SelectionLength). Wird etwas Text markiert, beginnt sie direkt hinter der Markierung. Sie können dieses Verhalten durch Umstellen des zweiten Parameters der Find-Methode auf die Zahl 1 ändern oder indem Sie einfach die zweiten zwei Parameter weglassen, wie es der case-Zweig »Alle ersetzen« in Zeile 97 tut.
Abbildung R1.2: NetWord in Aktion
Die nächsten zwei Fälle, ERSETZEN und ALLE ERSETZEN, funktionieren in ähnlicher Weise. ERSETZEN verwendet die Find-Methode, markiert den angegebenen Text und ersetzt ihn mit Hilfe der SelectedText-Eigenschaft. Daraufhin markiert es das gerade ersetzte Wort. ALLE ERSETZEN tut das Gleiche, nur in einer while-Schleife, so dass man
273
Woche 1 – Rückblick
alle Vorkommen statt nur eines ersetzen kann. Durch jede Iteration der Schleife suchen Sie den angegebenen Text, markieren und ersetzen ihn, woraufhin es zum nächsten Auftreten des gesuchten Textes geht – und so weiter, bis es keine Vorkommen davon mehr gibt (also bis Find –1 zurückgibt). Anschließend wird eine MessageBox angezeigt, um dem Benutzer mitzuteilen, wie viele Textvorkommen ersetzt wurden. Ihre Anwendung ist endlich vollständig. Erledigen Sie Kompilierung und Ausführung und erfreuen Sie sich an den Früchten Ihrer Arbeit. Abbildung R1.2 zeigt das Ergebnis dieser Anwendung.
Einige nützliche Dinge mit Pfiff hinzufügen Ihre Anwendung mag ja vollständig sein, doch sie ist nicht sonderlich benutzerfreundlich. Wir wollen mit ein paar Extrafunktionen NetWord etwas mehr Leben einhauchen. Zunächst fügen wir Ihren Menüelementen Tastenkombinationen (Shortcuts) hinzu. Fügen Sie den folgenden Code in den NetWord-Konstruktor ein: mniOpen.Shortcut = Shortcut.CtrlO; mniSave.Shortcut = Shortcut.CtrlS; mniPrint.Shortcut = Shortcut.CtrlP; mniUndo.Shortcut = Shortcut.CtrlZ; mniCut.Shortcut = Shortcut.CtrlX; mniCopy.Shortcut = Shortcut.CtrlC; mniPaste.Shortcut = Shortcut.CtrlV; mniFind.Shortcut = Shortcut.CtrlF; mniOpen.ShowShortcut = true; mniSave.ShowShortcut = true; mniPrint.ShowShortcut = true; mniUndo.ShowShortcut = true; mniCut.ShowShortcut = true; mniCopy.ShowShortcut = true; mniPaste.ShowShortcut = true; mniFind.ShowShortcut = true;
Die von uns zugewiesenen Tastenkombinationen entsprechen den Standardabkürzungstasten von Windows für die betreffenden Menüoptionen. Als nächsten Schritt erstellen wir ein Kontextmenü, das auf den bereits erzeugten Menüelementen (MenuItem-Steuerelementen) basiert. Je nachdem, was der Benutzer ausgewählt hat, werden unterschiedliche Optionen erscheinen. Das Erstellen des Kontextmenüs ist einfach, doch die Bestimmung dessen, was darin erscheinen soll, ist ein wenig kniffliger. Werfen Sie einen Blick auf folgenden Code:
274
Woche 1 – Rückblick
'in der Klasse private ContextMenu cmnuDocument = new ContextMenu(); ... 'im Konstruktor cmnuDocument.MenuItems.Add(mniCut.CloneMenu()); cmnuDocument.MenuItems.Add(mniCopy.CloneMenu()); cmnuDocument.MenuItems.Add(mniPaste.CloneMenu()); cmnuDocument.MenuItems.Add("-"); cmnuDocument.MenuItems.Add(mniFont.CloneMenu()); cmnuDocument.Popup += new EventHandler(this.HandleContext); rtbDocument.ContextMenu = cmnuDocument; ... 'eine neue Methode private void HandleContext(Object Sender,EventArgs e) { if (rtbDocument.SelectionLength == 0){ cmnuDocument.MenuItems [0 ].Enabled = false; cmnuDocument.MenuItems [1 ].Enabled = false; } else { cmnuDocument.MenuItems [0 ].Enabled = true; cmnuDocument.MenuItems [1 ].Enabled = true; } }
Die erste Zeile erstellt das ContextMenu-Objekt und die nächsten Zeilen weisen ihm diverse Menüelemente zu (mit dem Kontextmenü soll der Benutzer nur ausschneiden, kopieren, einfügen und die Schriftart ändern können). Beachten Sie, dass Sie die CloneMenu-Methode verwenden, um Kopien der fraglichen Menüelemente anzufertigen. Tun Sie dies nicht, dann richten Sie in Ihren Standard-Menüelementen ein Chaos an. Sie müssen das Kontextmenü dem Kontext der Anwendung anpassen (also dem, was der Benutzer gerade tut). Dies erlaubt Ihnen das Popup-Ereignis; es wird direkt vor der Anzeige des Menüs ausgelöst. Wir wollen uns die HandleContext-Methode ansehen, die das Popup-Ereignis handhabt. Es gilt zwei einfache Fälle zu bewerten. Ist in der RichTextBox kein Text markiert, dann sollte man die Menüs AUSSCHNEIDEN und KOPIEREN abschalten (sie sind dann nämlich nutzlos). Dies erfolgt einfach durch das Setzen der Enabled-Eigenschaft auf false – die Menüelemente sind dann zwar noch zu sehen, doch der Benutzer kann sie nicht auswählen. Ist jedoch Text ausgewählt, dann sollte man dafür sorgen, dass diese zwei Menüelemente aktiviert sind; sie sind das zwar per Voreinstellung, doch wenn beim Öffnen des Kontextmenüs kein Text markiert ist, werden sie ausgeschaltet. Mit Hilfe der else-Bedingung aktivieren Sie sie einfach wieder. Lassen Sie uns zum Schluss eine Statusleiste hinzufügen, die dem Benutzer Informationen liefert, etwa die aktuelle Zeilennummer oder die aktuelle Spalte in der Zeile, wo sich der Cursor befindet. Wir brauchen also eine Statuszeile (StatusBar) und einen Statuszeilenbereich (StatusBarPanel).
275
Woche 1 – Rückblick
private StatusBar sbarMain = new StatusBar(); private StatusBarPanel spnlLine = new StatusBarPanel();
Als Nächstes initialisieren Sie diese Steuerelemente im Konstruktor: sbarMain.ShowPanels = true; sbarMain.Panels.Add(spnlLine); spnlLine.AutoSize = StatusBarPanelAutoSize.Spring; spnlLine.Alignment = HorizontalAlignment.Right;
Nun ist Ihre Statusleiste eingerichtet, doch leider macht sie noch nichts. Da Sie die aktuelle Zeile und Spalte überwachen wollen, müssen Sie die Statusleiste bei jeder Änderung in der RichTextBox aktualisieren. Hierfür eignet sich das SelectionChanged-Ereignis hervorragend; es wird bei jeder Veränderung der Cursorposition ausgelöst (durch Tippen, Mausklicks, Drücken der (¢)-Taste usw.). Fügen Sie diesem Ereignis der RichTextBox einen Delegaten hinzu: rtbDocument.SelectionChanged += new EventHandler(this.UpdateStatus);
Der erste Teil der UpdateStatus-Methode kümmert sich um das Auffinden der Zeilennummer. Das ist ein recht einfacher Vorgang und lässt sich mit dem folgenden Code bewerkstelligen: private void UpdateStatus(Object Sender,EventArgs e) { String text = rtbDocument.Text; int intLine = rtbDocument.GetLineFromCharIndex(rtbDocument.SelectionStart) + 1;
Zuerst holen Sie den in der RichTextBox enthaltenen Text, um ihn leichter bearbeiten zu können. Das RichTextBox-Steuerelement verfügt über eine elegante Methode namens GetLineFromCharIndex, die Ihnen verrät, in welcher Zeile sich der Cursor gerade befindet. Dies liefert Ihnen die richtige Zeilennummer selbst dann, wenn es keine Zeilenumbrüche gibt. Wenn etwa die erste Zeile im Steuerelement über zwei Zeilen verläuft, werden diese beiden Zeilen 1 und 2 zurückgeben. Die Lines-Eigenschaft der RichTextBox lässt sich andererseits nicht zum Zeilenzählen verwenden, weil sie keine Zeilenumbrüche berücksichtigt (sie zählt zwei umbrochene Zeilen als eine Zeile). Die Zeichenposition bzw. Spaltennummer in der aktuellen Zeile zu ermitteln, ist ein wenig schwieriger. Um festzustellen, ob man sich am Zeilenanfang befindet, kann man die GetPositionFromCharIndex-Methode der RichTextBox verwenden, welche einen Point mit den Cursorkoordinaten zurückgeben wird. Ist die x-Koordinate gleich 1, dann befinden Sie sich am Rand ganz links außen oder, mit anderen Worten, am Zeilenanfang. Das folgende Codestück führt diese Prozedur aus: int intLine = rtbDocument.SelectionStart; while (rtbDocument.GetPositionFromCharIndex(i).X > 1) --i; String StrChar = (rtbDocument.SelectionStart – i + 1).ToString();
276
Woche 1 – Rückblick
Zuerst setzen wir die Variable i auf die aktuelle Cursorposition. In einer while-Schleife verringern wir i, solange die x-Koordinate für den Point bei Zeichen i größer als 1 ist. Schließlich subtrahieren wir i von unserer Ausgangsposition, um festzustellen, wie weit die Ausgangsposition vom Zeilenanfang entfernt ist. Weisen Sie zum Schluss die sich ergebende Zeichenfolge dem Statusleistenbereich zwecks Ausgabe zu: spnlLine.Text = "Zeile " + intLine.ToString() + " Spalte " + strChar;
Abbildung R1.3 zeigt das Ergebnis. Die Projekte in Woche 2 und 3 werden diese Anwendung erweitern. Am Ende der Woche 2 werden Sie Internetfähigkeiten hinzufügen und NetWord in eine MDI-Anwendung umwandeln. Danach lernen Sie, wie Sie Ihre Anwendung installieren und sie mit anderen Windows-Anwendungen zusammenarbeiten lassen.
Abbildung R1.3: In der Statusleiste werden nun die aktuelle Zeilennummer und Cursorposition in der Zeile korrekt angezeigt.
277
Tag 1
Mit Windows Forms beginnen
25
Tag 2
Windows Forms-Anwendungen erstellen
45
Tag 3
Mit Windows Forms arbeiten
77
Tag 4
Menüs und Symbolleisten
111
Tag 5
Ereignisse in Windows Forms
151
Tag 6
Windows Forms mit Steuerelementen erweitern
177
Tag 7
Mit Dialogfeldern arbeiten
231
Tag 8
Datenbindung in Windows Forms
281
T ag 9
ADO.NET einsetzen
309
Tag 10
MDI-Anwendungen erstellen
349
Tag 11
Arbeiten mit der Windows-Ein-/Ausgabe
377
Tag 12
Formulare Internet-fähig machen
415
Tag 13
Grafische Anwendungen mit GDI+
439
Tag 14
ActiveX
477
Tag 15
Webdienste
515
Tag 16
Windows-Dienste
539
Tag 17
Fortgeschrittene Techniken für Steuerelemente in Windows Forms
565
Benutzerdefinierte Steuerelemente in Windows Forms
589
Tag 19
Multithreading-Anwendungen
621
Tag 20
Windows Forms konfigurieren und bereitstellen
655
Tag 21
Debugging und Profiling
681
Tag 18
W O C H E
W O C H E
W O C H E
Woche 2: Funktionen hinzufügen Willkommen zu Ihrer zweiten Woche in der Gesellschaft von Windows Forms! In der vorigen Woche wurden Sie in die verschiedenen Konzepte eingeführt, die bei Windows Forms erforderlich sind. Sie erfuhren, worum es sich bei Windows Forms handelt, wie man sie erstellt und wie man Windows Forms-Steuerelemente verwendet. Nun, da Sie über die Grundlagen verfügen, ist es an der Zeit, zu fortgeschritteneren Themen überzugehen. In dieser Woche lernen Sie, wie Sie Ihren Anwendungen leistungsfähige Funktionen hinzufügen, darunter Datenbankfähigkeiten und viele andere moderne Merkmale. In der dritten Woche werden Sie dann das komplexe Innenleben von Windows Forms kennen lernen und erfahren, wie Sie es nutzen können, um richtig ausgefuchste Anwendungen zu schreiben. Der Tag 8 mit dem Motto »Datenbindung in Windows Forms« lehrt Sie, wie Windows Forms entworfen sind, um mit allen Datentypen umgehen zu können, ohne auf Datenbanken zugreifen zu müssen. Diese Daten können Sie auf jede erdenkliche Weise manipulieren. An Tag 9 mit dem Titel »ADO.NET« lernen Sie, wie Sie anhand des Wissens aus Tag 8 Datenbanken in Ihre Anwendungen integrieren. Tag 10 (»MDI-Anwendungen«) konzentriert sich auf Anwendungen mit der Mehrfachdokumentschnittstelle (Multiple Document Interface), kurz MDI. Mit diesem Applikationstyp können Sie mehrere Fenster zugleich geöffnet halten. »Arbeiten mit der Windows-Ein-/Ausgabe« ist das Motto des elften Tages. Danach beherrschen Sie die Interaktion mit Windows-Dateien – also Speichern, Öffnen, Überschreiben usw. – sowie das Drucken. Wie Sie das Internet in Ihren Anwendungen nutzen, lernen Sie an Tag 12, »Formulare Internet-fähig machen«. Sie erstellen einen einfachen Webbrowser und surfen nach Herzenslust. An Tag 13 unter dem Motto »Grafische Anwendungen mit GDI+« erfahren Sie etwas über GDI+, das Paradigma für die Handhabung von Windows-Grafikaspekten. Sie erlernen nicht nur das Erstellen von »zeichnenden« Anwendungen, sondern auch, auf welche Weise sich GDI+ auf Ihre normalen Anwendungen auswirkt. Tag 14 trägt den Titel »ActiveX«, so dass Sie nun lernen, wie Sie andere Anwendungen und Komponenten mit Ihren eigenen Windows Forms-Anwendungen integrieren! Lassen Sie uns in die zweite Woche starten!
Datenbindung in Windows Forms
8
Datenbindung in Windows Forms
Die Datenbindung ist ein interessanter Begriff in .NET; mit ihrer Hilfe können Sie Daten mit den Steuerelementen in Windows Forms verknüpfen. Diese Fähigkeit erlaubt es Ihnen wiederum, diese Daten leichter anzuzeigen und zu bearbeiten. Nach der heutigen Lektion werden Sie verstehen, dass Datenbindung ein leistungsfähiger Partner sein kann. Heute lernen Sie, 쐽
was man unter Datenbindung versteht,
쐽
wie Sie Daten mit Ihren Steuerelementen verknüpfen,
쐽
was ein DataGrid ist,
쐽
wie Sie feststellen, welche Daten ein Benutzer in einem Steuerelement ausgewählt hat.
8.1
Einführung in die Datenbindung
Das Konzept der Datenbindung zu verstehen, ist manchmal nicht einfach, daher wollen wir ein paar Augenblicke dafür aufbringen. Lassen Sie uns zunächst den Begriff Daten erörtern. In Bezug auf die Datenbindung sind Daten beliebige Arten von Informationen, mit denen eine Anwendung arbeitet (oftmals als Datenmodell bezeichnet). Es könnte sich also um die in Ihrer Anwendung verwendeten Farben, um die Kundenliste in einer Datenbank oder um die Text-Eigenschaft Ihres Formulars handeln. Alle diese Beispiele stehen für Informationen, die Sie in Ihren Anwendungen verarbeiten. In den meisten Fällen will man solche Daten auch einem Benutzer präsentieren. Wenn Sie etwa einen CD-Katalog erstellen, muss der Benutzer seine eigenen Eingaben sehen können. Sie können eine solche Möglichkeit hinzufügen, indem Sie den Text-Eigenschaften diverser Steuerelemente Werte zuweisen, Kombinationsfelder und Listenfelder hinzufügen usw. Die Definition von Daten ist recht unscharf; doch eine exaktere Erklärung ist schwierig, da Daten in so vielfältiger Form vorkommen. Das ist einer der Gründe, warum Datenbindung so wichtig und leistungsfähig ist. Denn einfach ausgedrückt, erlaubt Ihnen Datenbindung, Informationen in einem Windows Forms-Steuerelement zusammenzuführen. Diese Bindung stellt eine Lese-/Schreibbeziehung zwischen den Daten und dem Steuerelement her; das Steuerelement zeigt die Daten in einer benutzerfreundlichen Weise an, und Änderungen im Steuerelement spiegeln sich in den Daten wider. Durch die Datenbindung eines Steuerelements machen Sie es praktisch zum Vehikel für die Anzeige und Bearbeitung von Daten.
282
Einführung in die Datenbindung
Inwiefern unterscheidet sich also Datenbindung von der Wertezuweisung in einem Steuerelement? Für die einfache Datenbindung besteht kaum ein Unterschied. Doch wenn Sie einmal mit komplexer Datenbindung anfangen, erspart sie Ihnen viel Mühe, wenn Sie eine Benutzeroberfläche erstellen, wobei zugleich Ihr Code schlanker und einfacher wird. Stellen Sie sich ein typisches Szenario vor, etwa den erwähnten CD-Katalog. Gibt der Benutzer Informationen zu einem neuen Album ein, lassen sich diese direkt in einem TreeView-Steuerelement anzeigen (siehe Tag 6). Dieser Prozess ist zwar einfach und schnell, doch leider verlieren Sie auf diese Weise Funktionalität. Erstens verfügen Sie über keine besonders gute Aufzeichnung der Daten, außer im TreeView-Steuerelement, welches nicht gerade der optimale Speicherort ist. Und zweitens bleiben die Daten nach der Anzeige auch weiterhin im Steuerelement. Außer durch lange und komplizierte Prozeduren lassen sie sich dort nicht mehr bearbeiten. Statt die Informationen direkt anzuzeigen, können Sie sie auch in eine Datenbank eingeben, sobald der Benutzer sie eingetippt hat. Sie verfügen dann über eine Datenaufzeichnung, die Sie nach Wunsch bearbeiten und in so vielen Steuerelementen anzeigen können, wie Sie wollen, statt in einem einzigen TreeView-Steuerelement. Innerhalb dieses Rahmens verfügen Sie nun über zwei verschiedene Objekte: die Daten und die Steuerelemente, um diese Daten anzuzeigen. Die Daten können Sie an das Steuerelement binden und das Steuerelement realisiert dann alle Aspekte der Anzeige für Sie. Die Interaktion mit dem Steuerelement berührt die Daten direkt. Dieses zweite Modell bietet mehrere Vorteile. Sie erzielen eine Trennung der Anwendungsdaten von der Benutzeroberfläche (die jeweils als die Daten- und die Präsentationsbzw. Benutzeroberflächenschicht bezeichnet werden). Haben Sie sich bereits einmal über Anwendungsentwicklungsmodelle informiert, kennen Sie schon das Dreischichtenmodell: eine Schicht für die Präsentation (in der Benutzeroberfläche), eine für die Daten und eine (in der Mitte) für die Behandlung der Logik (Verarbeitungsregeln) und der Beziehungen zwischen den Daten und der Benutzeroberfläche (User Interface, UI). Durch diese Aufteilung wird Ihre Anwendung weitaus eleganter und anspruchsvoller. Dieses Modell lässt sich einfacher erstellen und verstehen. Sollte außerdem eine der Schichten veraltet oder überflüssig werden, können Sie sie leicht austauschen, ohne Ihre gesamte Anwendung komplett neu oder umschreiben zu müssen. Die Aufteilung in Benutzeroberfläche und Datenebene sollte stets zu Ihren Zielen bei der Anwendungsentwicklung gehören. Datenbindung ist ein ausgezeichnetes Hilfsmittel bei diesem Unterfangen.
Daten aus jeder beliebigen Quelle Daten können in vielfältiger Form auftreten und in den verschiedensten Transportformen vorkommen. Ob Ihre Daten nun aus einer Datenbank stammen oder willkürlich von einem Benutzer eingegeben werden, ist gleichgültig, denn beide können an Windows
283
Datenbindung in Windows Forms
Forms-Steuerelemente gebunden werden. Diese immense Flexibilität erspart Ihnen als Entwickler eine Menge Arbeit bei der Umwandlung von Datentypen und -formen. Sie können einfach die Daten anbinden und dann gehen. XML-Daten werden beispielsweise fast wie solche aus einer relationalen Datenbank behandelt. Es ist gleichgültig, ob einige Daten aus einer Textdatei stammen und die übrigen aus Oracle oder SQL Server – beide Datentypen lassen sich für die Anzeige leicht an Steuerelemente binden. Änderungen im Steuerelement lassen sich in die XML-Datei oder die Datenbank zurückschreiben, so dass man nie Informationen verliert. Wenn Sie morgen etwas über ADO.NET lernen, werden Sie verstehen, wie diese Lese-/Schreibverbindung bei Datenbanken funktioniert.
8.2
Einfache Datenbindung
Wie bereits erwähnt brauchen Sie für die Datenbindung ein paar Daten und ein Steuerelement, um sie daran zu binden. Bei den Daten ist lediglich darauf zu achten, dass sie in einer Form vorliegen, die die IList-Schnittstelle implementiert. Schnittstellen sind ja lediglich wie eine Anforderungsliste: Sie teilen Ihnen mit, was eine Klasse alles benötigt, sagen Ihnen aber nicht, wie Sie das »Zeug« erstellen sollen. Die IList-Schnittstelle definiert die Anforderungen für jedes Objekt, das Daten enthalten kann. Über 40 Objekte verwenden IList, angefangen bei Arrays und StringCollection-Auflistungen bis hin zu etwas selteneren Objekten wie etwa EventDecriptorCollection – für gewöhnlich haben Sie keine Mühe, ein IList verwendendes Objekt zu finden. Um Daten zu binden, fügen Sie der DataBindings-Auflistung ein neues Binding-Objekt hinzu. Dieser Vorgang verläuft genau so wie das Hinzufügen neuer Steuerelemente zur Controls-Auflistung eines Formulars. Alle Windows Forms-Steuerelemente verfügen über diese Auflistung, können also alle Datenbindung nutzen. Ein Blick auf die Syntax: steuerelement.DataBindings.Add("eigenschaft", objekt, "feld")
Steuerelement ist dabei natürlich das Steuerelement, für das Sie eine Datenbindung einrichten wollen. Der erste Parameter namens eigenschaft gibt an, an welche Eigenschaft des Steuerelements Sie Daten binden wollen. Das kann je nachdem variieren, welche Daten Sie anbinden wollen. So könnten Sie etwa eine Namensliste an die Text-Eigenschaft einer Schaltfläche binden, aber eine Farbenliste an die BackColor-Eigenschaft. Datenbindung ist flexibel genug für beides. Der zweite Parameter gibt die datenquelle an. Das kann ein Array, eine Datenbank, eine StringCollection usw. sein. Der dritte Parameter gibt an, welcher Teil – oder welches »Feld« – der Datenquelle gebunden werden soll. Für ein Array ergibt diese Information wenig Sinn, denn es enthält nur willkürliche Daten. Bei einer Datenbank könnten Sie aber etwa das Feld Name einer Tabelle binden. In diesem dritten Parameter würden Sie also »Name« angeben.
284
Einfache Datenbindung
In den nächsten Abschnitten lernen Sie konkretere Beispiele für Datenbindung kennen und erfahren, wie man gebundene Daten bearbeitet.
Eine Schaltfläche mit Daten verbinden Anhand eines simplen Beispiels verwenden wir für die Datenbindung ein Array. Das Beispiel ist in Listing 8.1 zu sehen. Listing 8.1: Eine Schaltfläche mit Daten verbinden 1: 2: 3: 4: 5: 6: 7: 8:
using System; using System.Windows.Forms; using System.Drawing;
namespace TYWinforms.Day8 { public class Listing81 : Form { private Button btData = new Button(); private Color[] arrColors = {Color.Green, Color.Blue,Color.Red, Color.Orange}; 9: 10: public Listing81() { 11: btData.Location = new Point(100,100); 12: btData.DataBindings.Add("BackColor", arrColors, ""); 13: btData.DataBindings.Add("Text", arrColors, ""); 14: 15: this.Controls.Add(btData); 16: } 17: 18: public static void Main() { 19: Application.Run(new Listing81()); 20: } 21: } 22: }
In Zeile 7 erstellen Sie das Steuerelement Button für die Datenbindung. In der Zeile darunter wird ein Array von Color-Objekten erzeugt und instantiiert: Grün, Blau, Rot und Orange. Sie haben also Ihr Steuerelement und die Daten dafür, nun können Sie sie miteinander verbinden. In Zeile 12 verwenden Sie die oben beschriebene Syntax, um das Array an die Schaltfläche zu binden. Beachten Sie die drei Parameter: Der erste gibt die Steuerelementseigenschaft für die Bindung an; da wir ein Array von ColorObjekten verwenden, binden Sie die Farben an die BackColor-Eigenschaft. Der zweite Parameter gibt die Datenquelle an: das in Zeile 8 erstellte Array. Beim
285
Datenbindung in Windows Forms
dritten Parameter handelt es sich lediglich um eine leere Zeichenfolge, da keine bestimmte Eigenschaft des Arrays gebunden werden muss (und kann); daher soll das gesamte Array angebunden werden. Sie haben nun die Farben an die Schaltfläche gebunden. Moment mal!. Zeile 13 scheint eine andere Datenbindungsprozedur aufzuweisen. Hier binden Sie das gleiche Farbenarray an die Text-Eigenschaft des Button-Steuerelements. Die CLR ruft automatisch eine ToString-Methode für die Array-Objekte auf, um die Text-Eigenschaft den Farben richtig zuzuweisen. Zum Schluss wird die Schaltfläche in Zeile 15 dem Formular hinzugefügt. Abbildung 8.1 zeigt das Ergebnis dieser Anwendung.
Abbildung 8.1: Die Datenbindung einer Eigenschaft veranlasst das Steuerelement zur Anzeige der Daten.
Was ist also geschehen? Die BackColor-Eigenschaft des Button-Steuerelements ist nun also auf die erste Farbe im Array, grün, gesetzt worden. Diese Farbe wird gesetzt, ohne dass man der BackColor-Eigenschaft manuell eine Farbe zuweisen müsste. Die Datenbindung kümmert sich selbst darum. Zweitens wird das Wort Green – das Sie sehen, wenn Sie Color.Green.ToString() aufrufen – als Beschriftung des Button-Steuerelements angezeigt. Und wieder brauchen Sie diese Eigenschaft nicht selbst zuzuweisen; das wird für Sie erledigt. Sie können noch mehr erreichen. Da das an das Button-Steuerelement gebundene Array mehr als eine Farbe enthält, können Sie alle Farben durchlaufen lassen. Zu diesem Zweck müssen Sie aber erst ein neues Objekt kennen lernen: BindingContext. In einem Steuerelement zeigt es die Beziehung zwischen Daten und der sie bindenden Steuerelementeigenschaft an. Es gibt an, welches Element in der Datenauflistung auf welche Weise angezeigt werden soll. Um seinen Gebrauch anzusehen, fügen Sie folgenden Code in das Listing 8.1 ein: //im Konstruktor btData.Click += new EventHandler(this.Cycle); ...
286
Einfache Datenbindung
//neue Methode public void Cycle(Object Sender, EventArgs e) { btData.BindingContext [arrColors ].Position += 1; }
Die erste Zeile fügt einen Ereignishandler für das Click-Ereignis hinzu. Sie benutzen dieses Ereignis, um alle Array-Elemente zu durchlaufen. Der Ereignishandler Cycle verwendet den BindingContext des Button-Steuerelements, um den gebundenen Wert zu ändern. BindingContext enthält eine Liste aller an das Steuerelement gebundenen Objekte, so dass Sie das korrekte angeben müssen; dieses referenziert das Array von Farben. Die PositionEigenschaft gibt an, welches Element gerade angezeigt wird, so dass Sie es nur um eins zu inkrementieren brauchen, um zum nächsten zu gelangen. Nun kompilieren Sie die Anwendung und klicken auf die Schaltfläche. Die Eigenschaften BackColor und Text des Button-Steuerelements ändern sich mit jedem Klick – und ohne dass Sie sich mit einer dieser Eigenschaften hätten einzeln abgeben müssen. Dieses Beispiel mag Ihnen ein wenig an den Haaren herbeigezogen erscheinen, doch wenn Sie eine Datenbank mit Tausenden von Kunden haben, können Sie sich vorstellen, wie diese einfache Datenbindungsprozedur das Entwicklerleben leichter machen kann. Zudem spiegeln sich alle im Array vorgenommenen Änderungen auch in der Schaltfläche wider. Sie könnten in Ihrer Anwendung etwa Folgendes hinzufügen: arrColors[2] = Color.Magenta
Die Schaltfläche würde sich dem ohne weiteres anpassen. Einfache Datenbindung ist also nicht schwer.
Das Bearbeiten gebundener Daten Wir wollen uns ein etwas anspruchsvolleres Beispiel ansehen, bei dem ein Kombinationsfeld verwendet wird, mit dem der Benutzer Elemente bearbeiten kann. Mit Kombinationsfeldern und anderen Listenanzeige-Steuerelementen gestaltet sich der Umgang mit den Daten etwas einfacher. Statt Datenbindungen zu entwerfen, können Sie einfach die DataSource-Eigenschaft einstellen, um die Standardeigenschaft des Steuerelements an die Standardeigenschaft der betreffenden Daten zu binden. Im Beispiel des Listings 8.2 präsentiert die Anwendung zwei Steuerelemente: ein Kombinationsfeld (ComboBox) zur Datenanzeige und eine Schaltfläche für die Datenbearbeitung. Wählt der Benutzer eine Farbe aus, kann er die Auswahl wieder ändern, indem er einen neuen Farbenwert in das Textfeld der ComboBox eintippt.
287
Datenbindung in Windows Forms
Listing 8.2: Komplexe Datenbindung 1: 2: 3: 4: 5: 6: 7: 8: 9:
using System; using System.Windows.Forms; using System.Drawing;
namespace TYWinforms.Day8 { public class Listing82 : Form { private ComboBox cboData = new ComboBox(); private Button btChange = new Button(); private Color[] arrColors = {Color.Green, Color.Blue, Color.Red, Color.Orange}; 10: private int intLastIndex; 11: 12: public Listing82() { 13: cboData.Location = new Point(50,75); 14: cboData.DropDownStyle = ComboBoxStyle.Simple; 15: cboData.DataBindings.Add("BackColor", arrColors, ""); 16: cboData.DataSource = arrColors; 17: cboData.SelectedIndexChanged += new EventHandler(this.ChangeColor); 18: 19: btChange.Location = new Point(175,75); 20: btChange.Text = "Ändern"; 21: btChange.Click += new EventHandler(this.EditColor); 22: 23: this.Controls.Add(cboData); 24: this.Controls.Add(btChange); 25: } 26: 27: public void ChangeColor(Object Sender, EventArgs e) { 28: cboData.BindingContext[arrColors].Position = cboData.SelectedIndex; 29: intLastIndex = cboData.SelectedIndex; 30: } 31: 32: public void EditColor(Object Sender, EventArgs e) { 33: if (Color.FromName(cboData.Text).IsKnownColor) { 34: arrColors[intLastIndex] = Color.FromName (cboData.Text); 35: 36: cboData.SelectedText = Color.FromName (cboData.Text).ToString(); 37: cboData.SelectedIndex = intLastIndex; 38: } 39: } 40: 41: public static void Main() { 42: Application.Run(new Listing82()); 43: } 44: } 45: }
288
Einfache Datenbindung
Der Anfang dieses Listings ähnelt dem von Listing 8.1. In Zeile 9 erzeugen Sie ein Farben-Array und eine Schaltfläche in Zeile 8. Doch binden Sie diesmal keine Daten an die Schaltfläche, sondern vielmehr an die ComboBox (Zeile 7). In Zeile 16 setzen Sie die DataSource-Eigenschaft auf das Farben-Array. Das ist alles, was Sie für das Datenbinden tun müssen. Wenn Sie dieses Steuerelement ansehen, entsprechen die Listenelemente den Farben im Array (und der angezeigte Text entspricht der Textdarstellung dieser Farben). Um das Steuerelement etwas zu beleben, binden Sie auch die BackColor-Eigenschaft der ComboBox wie beim Button-Steuerelement in Listing 8.1. Sobald sich der ausgewählte Index ändert, soll sich auch die Farbe des Kombinationsfeldes ändern, daher fügen Sie dem Ereignis SelectedIndexChange einen Ereignishandler hinzu. In den Zeilen 19 bis 21 richten Sie das Button-Steuerelement ein. Der Benutzer wird diese Schaltfläche zur Bestätigung seiner Datenänderungen in der ComboBox verwenden. Klickt er auf die Schaltfläche, modifizieren Sie die Daten und aktualisieren nach Bedarf auch das Kombinationsfeld. Die ChangeColor-Methode – der Ereignishandler für das SelectedIndexChangedEreignis – ist sehr einfach. Mit Hilfe der BindingContext-Eigenschaft der ComboBox setzen Sie die Position auf den gerade ausgewählten Index. Als Nächstes speichern Sie den Index für den späteren Gebrauch; sobald der Benutzer etwas in das Kombinationsfeld eintippt, verliert das gerade ausgewählte seine Markierung. Das führt zu Problemen, wenn Sie versuchen Elemente zu ändern, so dass Sie eine exakte Aufzeichnung des zuletzt ausgewählten Elements bereithalten müssen. In der EditColor-Methode spielen sich die wirklich interessanten Dinge ab. Als Erstes ändern Sie das Array; Sie übernehmen die neue vom Benutzer in das Kombinationsfeld eingegebene Farbe, rufen das damit verknüpfte Color-Objekt mit Hilfe der ColorFromName-Methode ab und weisen es dem ausgewählten Index im Array zu. Klickt der Benutzer nun wieder auf dieses Element, wechselt die Hintergrundfarbe auf den neuen Wert. Beachten Sie jedoch, dass sich der im Kombinationsfeld angezeigte Text nicht ändert, selbst wenn dies der aktuelle Farbwert tut. Der Text ändert sich nicht, weil die ComboBox über keinen Mechanismus verfügt, mit dem sie Änderungen in dem in ihrer DataSourceEigenschaft festgelegten Objekt aufspüren könnte. Daher müssen Sie einfach noch einmal den Text mit Hilfe der ColorFromName-Methode aktualisieren. Zum Schluss setzen Sie das aktuell ausgewählte Element auf das letzte vom Benutzer ausgewählte Element, welches jetzt der neue Wert ist. Kompilieren Sie Listing 8.2 und probieren Sie es aus. Abbildung 8.2 zeigt das Ergebnis.
289
Datenbindung in Windows Forms
Abbildung 8.2: Die ComboBox verwendet gebundene Daten für ihre Hintergrundfarbe.
Formulare mit Daten verbinden Sie werden häufig eine Datenquelle mit einer ganzen Anwendung verknüpfen wollen. Angenommen, Sie haben eine Applikation erstellt, die nur für die Anzeige eines CD-Katalogs verwendet wird. Die Anwendung verfügt über mehrere Steuerelemente, die jeweils mit den Aspekten einer einzelnen CD verbunden sind – eine Beschriftung für den Künstler, ein Textfeld für den Albumtitel, ein Textfeld für den laufenden Musiktitel usw. Sobald der Benutzer zum nächsten Album bzw. Datensatz weiterspringt, gibt es eine Alternative: Statt für jedes Steuerelement des Formulars den Wert BindingContext.Position ändern zu müssen, können Sie ihn einfach für das Form-Objekt ändern: 'VB .NET Me.BindingContext[object].Position += 1 //C# this.BindingContext[object].Position += 1; BindingContext ändert sich dann in jedem einzelnen Steuerelement, das im Formular
angebunden ist.
8.3
Das Steuerelement DataGrid
Das leistungsstarke DataGrid-Steuerelement wurde speziell für den Einsatz bei der Datenbindung entworfen. Es ist bei der Datenanzeige in allen unterschiedlichen Formaten effizient, dabei verfügt es über eine Vielzahl von Anzeigeoptionen, die Sie zur Anpassung einer Benutzeroberfläche verwenden können. Das DataGrid verfügt sogar über eingebaute Funktionen für das Bearbeiten, Hinzufügen und Löschen von Elementen.
290
Das Steuerelement DataGrid
Die Einrichtung des Steuerelements ist einfach: Setzen Sie seine DataSource-Eigenschaft auf die Datenquelle, und schon kann's losgehen. Listing 8.3 zeigt ein einfaches Beispiel auf der Grundlage der Listings 8.1 und 8.2. Listing 8.3: Datenbindung eines DataGrid-Steuerelements 1: 2: 3: 4: 5: 6: 7: 8:
using System; using System.Windows.Forms; using System.Drawing;
namespace TYWinforms.Day8 { public class Listing83 : Form { private DataGrid dgData = new DataGrid(); private Color[] arrColors = {Color.Green, Color.Blue, Color.Red, Color.Orange}; 9: 10: public Listing83() { 11: dgData.DataSource = arrColors; 12: dgData.Size = new Size(800,600); 13: 14: this.Size = new Size(800,600); 15: this.Controls.Add(dgData); 16: } 17: 18: public static void Main() { 19: Application.Run(new Listing83()); 20: } 21: } 22: }
Dieser Code ist fast identisch mit dem in Listing 8.1. Der Unterschied: Statt einer ComboBox verwenden Sie hier ein DataGrid. Kompilieren Sie das Listing und betrachten Sie das Ergebnis, wie es in Abbildung 8.3 zu sehen ist. Was ist nun passiert? Sie haben dem DataGrid bloß ein Array von Farben zugewiesen, also wo kamen dann die ganzen zusätzlichen Informationen her? Die in Abbildung 8.3 zu sehenden Spalten IsEmpty, A, B, IsNamedColorIsKnownColor, Name, G, R und IsSystemColor sind alles Eigenschaften des Color-Objekts. Ungeachtet seiner Farbe verfügt jedes ColorObjekt über diese Eigenschaften. Da Sie dem DataGrid eine Sammlung von Color-Objekten übergaben, nahm es an, dass Sie alle Eigenschaften angezeigt haben wollten. Hätten Sie ihm ein Array von Zeichenfolgen übergeben, würde es einfach die Länge des Textinhalts dieser Zeichenfolgen anzeigen. Tabelle 8.1 führt die gebräuchlichsten UI-Eigenschaften des DataGrid-Steuerelements auf.
291
Datenbindung in Windows Forms
Abbildung 8.3: Das DataGrid zeigt mühelos umfangreiche Datenmengen an. Eigenschaft
Beschreibung
AlternatingBackcolor
Legt die Farbe für jede zweite anzuzeigende Zeile fest (damit der Benutzer die Zeilen besser unterscheiden kann).
BackColor
Legt fest, welche Hintergrundfarbe das Datenblatt an.
BackGroundColor
Legt fest, welche Hintergrundfarbe die Fläche außerhalb des Datenblattes hat.
BorderStyle
Gibt einen der Werte der BorderStyle-Aufzählung an.
CaptionBackColor
Legt fest, welche Hintergrundfarbe der Titelbereich hat.
CaptionFont
Legt die Schriftart für den Titelbereich fest.
CaptionForeColor
Legt die Farbe der Schriftart im Titel fest.
CaptionText
Enthält den im Titel anzuzeigenden Text.
CaptionVisible
Gibt an, ob die Titelzeile sichtbar ist
ColumnHeadersVisible
Gibt an, ob die Spaltenköpfe sichtbar sind.
FlatMode
Gibt an, ob das Datenblatt im Flachmodus angezeigt wird (dieser Modus zeigt keine sichtbaren Linien zwischen Spalten- und Zeilenüberschriften an).
ForeColor
Gibt die Schriftfarbe im Datenblatt an.
GridLineColor
Gibt die Farbe der Datenblattlinien an.
GridLineStyle
Gibt einen der Werte der DataGridLineStyle-Aufzählung an: None: keine Datenblattlinien; Solid: durchgehende Datenblattlinien.
HeaderBackColor
Legt die Hintergrundfarbe der Zeilen- und Spaltenköpfe fest.
HeaderFont
Legt die Schriftart der Spaltenköpfe fest.
HeaderForeColor
Legt die Schriftfarbe der Kopfzeile fest.
Tabelle 8.1: Eigenschaften der DataGrid-Benutzeroberfläche
292
Das Steuerelement DataGrid
Eigenschaft
Beschreibung
LinkColor
Gibt die Farbe von Links an, mit deren Hilfe man zu untergeordneten Tabellen navigieren kann (mehr dazu morgen).
LinkHoverColor
Gibt die alternative Farbe von Links an, falls der Mauszeiger darüber schwebt.
ParentRowsBackColor
Legt die Hintergrundfarbe von übergeordneten Zeilen fest.
ParentRowsForeColor
Legt die Schriftfarbe von übergeordneten Zeilen fest.
ParentRowsLabelStyle
Enthält einen Wert der DataGridParentRowsLabelStyle-Aufzählung: ColumnName: der Name der übergeordneten Spalte TableName: der Name der übergeordneten Tabelle Both: beide Namen None: weder der eine noch der andere Name
ParentRowsVisible
Gibt an, ob übergeordnete Zeilen sichtbar sind.
PreferredColumnWidth
Legt die Breite angezeigter Spalten fest.
PreferredRowHeight
Legt die Höhe angezeigter Zeilen fest.
RowHeadersVisible
Gibt an, ob Titel für Zeilen sichtbar sind.
RowHeaderWidth
Legt die Breite von Zeilentitelzellen fest.
SelectionBackColor
Legt die Hintergrundfarbe einer ausgewählten Zelle fest.
SelectionForeColor
Legt die Schriftfarbe einer ausgewählten Zelle fest.
Tabelle 8.1: Eigenschaften der DataGrid-Benutzeroberfläche (Forts.)
Ein kurzer Blick auf ADO.NET Bevor Sie die Interaktion mit dem DataGrid-Steuerelement erlernen, sollten Sie noch einiges mehr über komplexe Datenstrukturen wissen. Arrays mögen ja schön und gut sein, sind aber leider nicht ganz optimal für die Speicherung und Darstellung von Daten. Sie lassen sich nicht vollständig modifizieren, und Sie können zwar einzelne Elemente im Array ändern, sie aber nicht vollständig entfernen. Wir werfen daher einen kurzen Blick auf einige der Objekte, die Teil von ADO.NET sind, insbesondere auf DataTable und seine Begleiter. Wenn Sie die morgige Lektion beginnen, haben Sie einen Vorsprung.
293
Datenbindung in Windows Forms
Eine DataTable ist eine vollständige Darstellung von Daten in einer Tabelle. Stellen Sie es sich als zweidimensionales Array mit Namen vor. Werfen Sie einen Blick auf Tabelle 8.2, die Informationen über Namen und Handorientierung bereithält. Vorname
Nachname
Handorientierung
Walter
Saravia
Rechtshänder
Eva
Payne
Rechtshänder
Joel
Andres
Linkshänder
Tabelle 8.2: Musterdaten
Diese Tabelle weist drei Datenzeilen und drei Spalten auf, die »Vorname«, »Nachname« und »Handorientierung« heißen. Aus dieser Tabelle können Sie ersehen, dass eine Spalte einen Informationstyp darstellt und eine Zeile die eigentlichen Daten (die Zeile wird im Deutschen auch als »Datensatz« bezeichnet). Eine DataTable ist genau das Gleiche. DataRow-Objekte stellen Zeilen in der DataTable dar und DataColumn-Objekte die Datentypen. Das DataGrid ist demnach optimal für die Informationsdarstellung in einer DataTable geeignet, denn es zeigt Daten im Tabellenformat an, wie Sie noch aus Abbildung 8.3 wissen. Wir wollen lernen, wie man Daten in einer DataTable speichert; diese Prozedur wird Ihnen sehr bekannt vorkommen. Sie müssen zuerst Ihre DataTable erstellen – das geht wie bei jedem anderen Objekt: //C# DataTable objMyTable =new DataTable("Meine Tabelle "); 'VB .NET dim objMyTable as New DataTable("Meine Tabelle ")
Die in Klammern stehenden Wörter stellen den Tabellennamen dar (sie werden für das spätere leichte Auffinden gebraucht). Als Nächstes erzeugen Sie DataColumn-Objekte, die jeweils einen der zu speichernden Informationstypen darstellen, und fügen Sie Ihrer DataTable hinzu. Dieser Vorgang ähnelt dem Hinzufügen von Steuerelementen zu einem Formular: //erzeugen Sie eine Spalte für Integer DataColumn ColumnA = new DataColumn(); ColumnA.DataType = System.Type.GetType("System.Int32 "); ColumnA.ColumnName = "Spalte A "; //erzeugen Sie eine Spalte für Zeichenfolgen DataColumn ColumnB = new DataColumn(); ColumnB.DataType = System.Type.GetType("System.String "); ColumnB.ColumnName ="Spalte B ";
294
Das Steuerelement DataGrid
//fügen Sie sie der Datentabelle hinzu objMyTable.Columns.Add(ColumnA); objMyTable.Columns.Add(ColumnB);
Nun verfügen Sie über eine vollständige, wenngleich etwas leere DataTable (also eine ohne Daten). Sie wird je zwei Datenelemente pro Zeile oder »Datensatz« enthalten: einen Integerwert und einen Zeichenfolgenwert. Um Daten hinzuzufügen, integrieren Sie einfach DataRows. Das folgende Stück Code zeigt eine einfache Möglichkeit die DataTable mit Daten zu füllen, indem man eine for-Schleife verwendet: DataRow myDataRow; for (int i =0; i <User> <UserID /> <User>
Diese Grundstruktur wird als XML-Schema bezeichnet. (Tatsächlich liegen die Dinge etwas komplizierter als hier, die detaillierte Darstellung ginge aber über den Rahmen dieses Buches hinaus.) Mit Hilfe dieses Schemas können Sie daraufhin alle Daten in Ihren Tabellen darstellen: <Users> <User> Chris Payne <UserID>1 27. Juni <User> Eva Payne <UserID>2 15. Juli ... ...
Dieser Code lässt sich von jeder Person mit einer Textverarbeitung (wie etwa NotePad) lesen, wohingegen die entsprechende Datenbanktabelle nur von jemandem gelesen werden kann, der diese bestimmte Datenbankanwendung benutzt oder die Tabelle für eine weitere Datenbankanwendung konvertiert. XML ist eine wirkungsvolle, implementierungsunabhängige Möglichkeit, Daten zu speichern und zu senden – sicherlich einer der Gründe, warum es im Web ein so großer Erfolg geworden ist. Daher erscheint es nur folgerichtig, dass auch Datenbanken und ihre Schnittstellen diese Kommunikationsmethode übernehmen.
313
ADO.NET einsetzen
ADO.NET setzt XML bei jedem Datenaustausch und für interne Darstellungen ein. Sobald Daten aus einer Datenbank geholt werden, werden sie in XML dargestellt und an jeden gewünschten Bestimmungsort geschickt. Da jede Anwendung XML leicht umwandeln kann, sorgt diese Vorgehensweise für eine breitgefächerte Kompatibilität Ihrer Daten.
9.2
Eine Datenbank anlegen
Für die Zwecke dieser Lektion werden Sie es hilfreich finden, eine Datenbank zu haben, die Sie für das Testen der Beispiele verwenden können. Daher beschreibt dieses Unterkapitel, wie man eine Datenbank sowohl in Microsoft Access als auch in SQL Server 2000 erstellt, zwei der meistverbreiteten und am besten erhältlichen Datenbanksysteme.
Eine Access-Datenbank Sie werden die Datenbank, die Sie hier erstellen, in späteren Lektionen wiederverwenden, daher wollen wir etwas Brauchbares entwerfen. Zuerst erzeugen Sie eine Benutzer-Tabelle zur Speicherung folgender Informationen: 쐽
VorName
쐽
NachName
쐽
Adresse
쐽
Stadt
쐽
Bundesland
쐽
PLZ
쐽
Telefonnummer
Diese typische Benutzer-Datenbank lässt sich auf viele verschiedene Anwendungen übertragen. Um die Datenbank in Access zu starten, rufen Sie Access auf und wählen NEU aus dem DATEI-Menü. Wenn Sie das Dialogfeld in Abbildung 9.2sehen, wählen Sie darin die Option LEERE ACCESS-DATENBANK aus, nennen sie TYWinForms.mdb und speichern sie im Ordner C:\Winforms\daten. (Der Ordner ist beliebig wählbar; doch die Beispiele in diesem Buch verweisen auf diesen Ordner.) Nach einem Klick auf OK sollten Sie die folgenden Optionen sehen können: Erstellt eine Tabelle in der Entwurfsansicht, Erstellt eine Tabelle unter Verwendung des Assistenten und Erstellt eine Tabelle in der Datenblattansicht. Wählen Sie Entwurfsansicht und fangen Sie an, Ihre Spalten einzutragen. Tragen Sie VorName in die Spalte FELDNAME ein und wählen
314
Eine Datenbank anlegen
Sie TEXT in der Spalten DATENTYP. Optional können Sie für dieses Feld eine Beschreibung eingeben wie etwa Benutzervorname. Füllen Sie die übrigen Felder der obigen Liste aus, dann sollten Sie eine Tabelle ähnlich der in Abbildung 9.3 erhalten.
Abbildung 9.2: Ausgehend von diesem Dialogfeld können Sie eine neue, leere Datenbank erstellen.
Abbildung 9.3: Hier geben Sie die Feldinformationen der Tabelle ein.
Fügen Sie schließlich eine weitere Spalte namens UserID hinzu, die Ihre Identitätsspalte ist (jeder Wert hier ist eindeutig). Weisen Sie ihr den Datentyp AutoWert zu und rechtsklicken Sie auf den schwarzen Pfeil neben dem Namen. Wählen Sie aus dem Menü PRIMÄRSCHLÜSSEL aus und beachten Sie, wie das Schlüsselsymbol neben dem Namen erscheint. Als Nächstes speichern Sie die Tabelle mit dem Befehl DATEI/SPEICHERN. Der Dateiname sei tblUsers. Wählen Sie zum Schluss DATEI/SCHLIEßEN, um die Tabelle tblUsers zu schließen.
315
ADO.NET einsetzen
Wenn Sie einen Primärschlüssel für mehr als eine Spalte definieren wollen, dann markieren Sie jede Spalte bei gedrückter (Strg)-Taste und rechtsklicken dann, um den Primärschlüssel auszuwählen. Sie können nun Benutzer leicht hinzufügen, indem Sie auf die tblUsers-Option doppelklicken, die im Hauptfenster erscheint. Wenn Sie Daten eingeben, brauchen Sie keinen Wert für die Spalte UserID einzugeben. Eine Zahl wird automatisch eingefügt und bei jedem neuen Datensatz inkrementiert, wie in Abbildung 9.4 zu sehen. Tabelle 9.2 führt die verschiedenen in Access 2000 angebotenen Datentypen auf. Ähnliche Datentypen sind in sämtlichen Datenbanksystemen zu finden, wenn auch die konkreten Namen variieren können. Datentyp
Beschreibung
Autonumber (AutoWert)
Eine automatisch inkrementierte Ganzzahl (Integer), um Zeilen in einer Tabelle eindeutig zu identifizieren.
Currency (Währung)
Wird zur Speicherung von Währungsdaten verwendet.
Date/Time (Datum/Uhrzeit)
Wird für die Speicherung von Datums- und Zeitwerten verwendet. Access 2000 ist in der Lage, Jahreswerte zwischen 100 und 9999 zu speichern.
Memo
Speichert bis zu 65535 alphanumerische Zeichen.
Number (Zahl)
Speichert numerische Werte.
Text
Speichert bis zu 255 alphanumerische Zeichen.
Yes/No (Ja/Nein)
Wird für Spalten verwendet, die nur einen von zwei Werten haben können (ähnlich zu Bit- oder booleschen Typen)
Tabelle 9.2: Die Datentypen in Access 2000
Abbildung 9.4: Sie können jetzt in Ihre neue Tabelle Daten eingeben.
316
Eine Datenbank anlegen
Eine Datenbank in SQL Server 2000 anlegen Das Anlegen einer Datenbank in SQL Server 2000 ist ein wenig komplizierter als in Access, doch der Ablauf ist im Wesentlichen der gleiche. Access ist Ihren individuellen Ansprüchen angemessen, doch SQL Server 2000 wird in der Regel für umfangreichere Datenbanken verwendet, die mehr Sicherheit und Stabilität erfordern. Wir wollen nun die tblUser-Datenbank in SQL Server 2000 erstellen. In Ihrem Startmenü finden Sie die Menügruppe MICROSOFT SQL SERVER: Wählen Sie die Menüoption ENTERPRISE MANAGER. Sofern alles korrekt eingerichtet wurde, sollten Sie ein dem Windows Explorer ähnliches Navigationssystem sehen können, das oben MICROSOFT SQL SERVERS zeigt und den Namen Ihres Servers ein paar Hierarchieebenen darunter. Erweitern Sie den DATENBANKEN-Ordner (siehe Abbildung 9.5) und wählen Sie die Option NEUE DATENBANK aus dem Menü VORGANG bzw. aus dem Menü AKTION, wenn Sie den Enterprise Manager auf dem Client ausführen. (Sie können auch das Kontextmenü nutzen).
Abbildung 9.5: Um Ihre Datenbank zu erstellen, erweitern Sie den Ordner DATENBANKEN und wählen die Option NEUE DATENBANK aus dem Kontextmenü für Vorgänge bzw. Aktionen aus.
Geben Sie als Namen TYWinforms in das Dialogfeld ein, das erscheint. Im Moment akzeptieren Sie einfach die Standardoptionen der Registerkarte ALLGEMEIN und klicken auf OK. SQL Server 2000 wird eine neue Datenbank und ein Transaktionsprotokoll für Sie anlegen. Sie sollten nun TYWINFORMS im Ordner DATENBANKEN sehen können. Als Nächstes erweitern Sie diesen Eintrag in der Strukturansicht und wählen dann den TABELLEN-Knoten.
317
ADO.NET einsetzen
Sie bemerken, das SQL Server 2000 bereits einige Tabellen angelegt hat. Es handelt sich um Systemtabellen, die SQL Server benutzt, um die Elemente, die Sie in die Tabelle stellen, zu registrieren. Ignorieren Sie diese Tabellen für den Moment und wählen Sie NEUE TABELLE aus dem VORGANG-Menü aus. Sie sehen ein Gitter, das dem in Access gezeigten ähnelt. Geben Sie die Informationen wie in Access ein (siehe Abbildung 9.6), nur dass Sie diesmal den varchar-Datentyp anstelle von Text verwenden, denn Text hat in SQL Server 2000 eine andere Bedeutung. Beachten Sie, dass SQL Server 2000 weitaus mehr Datentypen bereitstellt als Access. Dieser Server ist eine leistungsfähigere Anwendung, bei der Entwickler die Datenbankoptionen mit viel größerer Feinheit einstellen können. Markieren Sie im SPALTEN-Bereich in der unteren Dialogfeldhälfte (siehe Abbildung 9.6) das UserID-Feld und wählen Sie JA für die Eigenschaft Identität. Dann klicken Sie auf dieses Feld mit der rechten Maustaste und wählen PRIMÄRSCHLÜSSEL FESTLEGEN aus. Speichern Sie als Nächstes diese Tabelle als tblUsers, indem Sie die Entwurfsansicht mit einem Klick auf die Steuerschaltfläche X rechts oben schließen, die Frage nach dem Speichern der Tabelle mit JA beantworten und dann den gewünschten angeben. Um in Ihre Tabelle Daten eingeben zu können, klicken Sie mit der rechten Maustaste auf die Tabelle und wählen TABELLE ÖFFNEN/ALLE ZEILEN ZURÜCKGEBEN aus, um einen tabellarischen Dateneingabemechanismus ähnlich dem in Access zu öffnen. Tragen Sie ein paar Namen und Adressen ein. (Tragen Sie jedoch nichts im UserID-Feld ein.) Klicken Sie auf das Ausrufezeichen (= Ausführen) im Menü, um die UserIDs automatisch zu erhöhen. Schließen Sie zu guter Letzt Ihre neue Tabelle durch einen Klick auf die SCHLIEßENSteuerschaltfläche.
Abbildung 9.6: Um eine Tabelle zu erstellen, geben Sie Feldnamen und Datentypen ein.
318
SQL für die Datenbankabfrage
Ich empfehle
Bitte beachten Sie
Verwenden Sie SQL Server, wenn Sie eine Setzen Sie SQL Server nicht für sehr einfache umfangreiche Datenbanklösung entwickeln, für Datenbanken mit nur wenigen Tabellen ein. die Performance wichtig ist und die fortgeschrit- Das führt zu Komplikationen, die unnötig sind. tene Datenbankfunktionen erfordert.
9.3
SQL für die Datenbankabfrage
Die Structured Query Language (SQL) ist ein Industriestandard, um an eine Datenbank Befehle auszugeben. Anwendungen wie SQL Server, Access und Oracle verwenden gleichermaßen SQL, um Daten zu manipulieren (wiewohl jede ihre eigene Methode hat, SQL einzusetzen). Die grundlegenden Fähigkeiten von SQL bestehen im Abrufen, Bearbeiten und Löschen von Daten, doch in Wirklichkeit handelt es sich um eine leistungsfähige und komplizierte Sprache, von der wir aber einen Großteil nicht in diesem Buch besprechen werden. Um aber mit ADO.NET arbeiten zu können, benötigen Sie eine gewisse Vertrautheit mit SQL, und die folgenden Abschnitte bilden eine kurze Einführung. Wie jede Programmiersprache verfügt auch SQL über Statements, die ausgeführt werden (doch anders als etwa C# muss SQL nicht kompiliert werden). Die vier wichtigen Statements, die Sie zwingend für die Zwecke dieser Lektion kennen müssen, sind SELECT, DELETE, INSERT und UPDATE; zum Glück folgen sie alle einer ähnlichen Syntax.
Das SELECT-Statement SELECT ist wohl das gebräuchlichste SQL-Statement. Sein Zweck ist im Grunde, eine Reihe von Daten aus einer Datenbanktabelle zu holen. Die Syntax dafür sieht so aus: SELECT Auswahlliste FROM Tabellenname [WHERE Suchbedingung] ORDER BY Sortierausdruck [ASC | DESC ]}
Die Struktur folgt grundlegenden Mustern des Englischen. Auswahlliste ist im Allgemeinen eine durch Kommas getrennte Liste der Spalten, die Sie abrufen wollen. VorName, NachName würde so etwa die Werte in diesen beiden Spalten zurückgeben. Zudem können Sie ein Sternchen verwenden, um alle Spalten einer bestimmten Tabelle (siehe Tipp) zurückzugeben. Tabellenname ist der Name derjenigen Tabelle, aus der die Daten geholt werden sollen.
319
ADO.NET einsetzen
Hier ist ein einfaches SELECT-Statement, das Sie für die von Ihnen angelegten Datenbanken verwenden könnten: SELECT * FROM tblUsers
Dieses Statement gibt alle Zeilen aller Spalten in der tblUsers-Tabelle zurück. Sie können damit tun, was Sie wollen. Das folgende Statement ist in der Funktion identisch: SELECT UserID, VorName, NachName, Adresse, Stadt, Bundesland, PLZ, Telefonnummer FROM tblUsers
Sie sollten SELECT * nicht verwenden, weil es oft mehr Daten liefert als Sie brauchen, und das kann zu reduzierter Performanz führen. Sie sollten statt dessen ausdrücklich nur diejenigen Spalten angeben, die Sie brauchen. Das ist zwar mehr Mühe beim Codeschreiben, doch Ihr Code wird dafür schneller ausgeführt. SELECT-Statements geben stets alle Zeilen in der Datenbank zurück, es sei denn, man schränkt dies durch eine WHERE-Bedingung ein. Sie können WHERE verwenden, um zusätzliche Kriterien für die zurückgegebenen Daten anzugeben. Eine WHERE-Bedingung kann im Grunde jedes logische Statement sein, ähnlich wie das if-Statement in normalen Programmiersprachen. Hier ein Beispiel: SELECT FROM tblUsers WHERE VorName = 'Chris' SELECT FROM tblUsers WHERE Stadt = 'ASP-Stadt' und NachName = 'Mitchell'
Das erste Statement gibt nur Datensätze zurück, in denen das Feld VorName den Wert Chris aufweist. Das zweite Statement gibt nur diejenigen Datensätze zurück, in denen Stadt gleich ASP-Stadt ist und das NachName-Feld Mitchell enthält. Um Jokerzeichen für Ihre WHERE-Bedingung zu verwenden, gibt es das LIKE-Schlüsselwort gefolgt von (einem oder mehreren) Jokerzeichen. Die Tabelle 9.3 führt die Operatoren auf, die das LIKE-Schlüsselwort akzeptiert. Operator
Übersetzung
*
Eine Zeichenfolge von null oder mehr Zeichen (in SQL Server wird dieses Zeichen durch % dargestellt).
_ (ein Unterstrich)
Genau ein Zeichen.
[]
Ein bestimmtes Zeichen in vorgegebener Menge.
Tabelle 9.3: Jokerzeichen für den LIKE-Operator in Access
320
SQL für die Datenbankabfrage
Die folgenden Statements demonstrieren den Gebrauch von LIKE: 1 2 3
SELECT * FROM tblUsers WHERE VorName LIKE '*s*' SELECT * FROM tblUsers WHERE VorName LIKE '_hris' SELECT * FROM tblUsers WHERE VorName LIKE '[abcd]hris'
Das SQL-Statement in Zeile 1 gibt jede Zeile zurück, in der der Vorname im Wert ein s enthält. Der Sternchen-Operator * funktioniert in SQL auf gleiche Weise wie in Windows und bedeutet null oder mehr Zeichen. Das Statement in Zeile 2 gibt alle Datensätze zurück, in denen der Vorname ein Zeichen enthält, das von der Zeichenfolge hris gefolgt wird. Auf Grund der vorliegenden Daten ergibt dieses Statement nur einen Datensatz, aber Sie sehen den Sinn. Zeile 3 gibt jeden Datensatz zurück, in dem der Vorname eine Zeichenfolge enthält, die mit einem der Buchstaben a, b, c oder d beginnt, gefolgt von der Zeichenfolge hris. Die ORDER BY-Klausel gibt die Reihenfolge an, in der die Daten zurückgegeben werden, so etwa in alphabetischer Reihenfolge. Verwendet man diese Bedingung nicht, kann man nie wissen, in welcher Reihenfolge man die Daten aus der Datenbank erhält. Mögen sie auch in alphabetischer Reihenfolge eingegeben worden sein, so heißt dies noch lange nicht, dass man sie auch in dieser Reihenfolge wieder herausbekommt. Mit der ORDER BY-Klausel können Sie festlegen, wie die Spalten sortiert werden, etwa so: SELECT * FROM tblUsers ORDER BY VorName ASC
Dieses Statement gibt alle Datensätze in alphabetisch nach Vornamen sortierter Reihenfolge zurück. Die Sortieroption DESC dreht diese Reihenfolge um (ASC = ascending, aufsteigend; DESC = descending, absteigend). Sie können mehrere, durch Komma abgegrenzte Spaltennamen in der ORDER BY-Klausel angeben, und zwar für jede Spalte mit ihrer eigenen Sortierreihenfolge. SQL verwendet die zusätzlichen Spalten, falls die erste duplizierte Daten (Verdopplungen) enthält. Geben Sie aber keinesfalls Spalten an, die nicht in der Tabelle vorhanden sind. Sie können SELECT-Statements einsetzen, um Daten aus mehr als einer Tabelle auf einmal zurückzugeben. Für das Tabellenname-Argument verwenden Sie dann einfach eine durch Kommas getrennte Liste von Tabellennamen. Normalerweise bestehen zwischen mehreren Tabellen Beziehungen, aber dieses Thema ginge über das Anliegen dieses Kapitels hinaus. Alle anderen SQL-Statements, die wir heute betrachten, folgen derselben grundlegenden Form wie die SELECT-Abfrage. Daher verwenden wir weniger Zeit darauf.
321
ADO.NET einsetzen
Das INSERT-Statement Ein weiteres gebräuchliches SQL-Statement ist INSERT, das neue Zeilen (also neue Daten) in eine Datenbanktabelle einfügt. Seine grundlegende Syntax sieht wie folgt aus: INSERT [INTO] Tabellenname [(Spaltenliste)] VALUES (DEFAULT | NULL | Ausdruck)
Dieses problemlose Statement ähnelt der Syntax von SELECT. Wir wollen ein Beispiel auf der Basis unserer Benutzer-Datenbank erstellen: INSERT INTO tblUsers (VorName, NachName, Adresse, Stadt, Bundesland, PLZ, Telefonnummer) VALUES ('Chris', 'Payne', 'ASP-Straße 135', ASP-Stadt', 'Florida', '36844', '8006596598')
Dieses Statement fügt eine neue Zeile mit den in der VALUES-Klausel angegebenen Feldwerten ein. Der in VALUES gelieferte Datentyp muss mit dem Datentyp in der entsprechenden Spalte übereinstimmen, oder man erhält eine Fehlermeldung. Wenn Sie eine Spalte angeben, müssen Sie auch einen Wert angeben, oder Sie bekommen noch eine Fehlermeldung.
Das UPDATE-Statement Das UPDATE-Statement aktualisiert vorhandene Zeilen in einer Datenbanktabelle. Sie können hier eine WHERE-Bedingung einbauen, um nur eine Untermenge der Tabellenwerte zu aktualisieren. Die Syntax sieht wie folgt aus: UPDATE Tabellenname SET Spaltenname = (DEFAULT | NULL | Ausdruck) [WHERE Suchbedingung]
Lassen Sie die WHERE-Bedingung weg, werden die angegebenen Spalten in jeder Zeile der Datenbank aktualisiert. Setzen Sie dieses Statement nur mit größter Vorsicht ein, denn damit können Sie Ihre Daten komplett ruinieren! Indem wir wieder unsere Datenbanktabelle tblUser verwenden, lässt sich UPDATE im Beispiel so verwenden: UPDATE tblUsers SET Adresse = 'ASP-Straße 136', 'Stadt = ASP-Weiler' WHERE VorName = 'Chris' AND NachName = 'Payne'
Dieses Statement ändert die Werte Adresse und Stadt für diejenigen Datensätze, in denen das Feld VorName den Wert Chris und das Feld NachName den Wert Payne enthält. Das ist momentan nur ein einziger Datensatz. Sie können nur die in der Spaltenname-Liste benötigten Spalten angeben. Die WHERE-Bedingung ist identisch mit der für das SELECT-Statement; Sie können auch hier wieder Jokerzeichen verwenden.
322
Einführung in DataSet
Das DELETE-Statement Das letzte hier behandelte Statement ist DELETE, welches Zeilen aus einer Datenbank löscht. Die Syntax sieht wie folgt aus: DELETE FROM Tabellenname WHERE (Suchbedingung)
In diesem einfachen Statement gibt es lediglich die sehr wichtige WHERE-Bedingung zu beachten. Denn wird sie nicht angegeben, löscht DELETE alle Zeilen aus der gesamten Datenbanktabelle – ein recht katastrophales Ereignis (außer wenn man eine Sicherheitskopie hat). Wieder ist die WHERE-Bedingung die gleiche wie für SELECT – ein Beispiel: DELETE FROM tblUsers WHERE VorName = 'Chris'
Dieses Statement löscht nur Datensätze, in denen der VorName auf Chris lautet.
9.4
Einführung in DataSet
In ADO.NET dreht sich alles um das DataSet. Dieses Objekt ist ein brandneues Konzept, das das althergebrachten Recordset aus ADO ersetzt. Das DataSet ist ein einfacher speicherresidenter Datenspeicher, der ein einheitliches Programmiermodell für den Datenzugriff bereitstellt, unabhängig vom jeweiligen Datentyp. Stellen Sie sich DataSet als eine Art Miniaturdatenbank vor. Anders als das Recordset enthält das DataSet vollständige Datenmengen, darunter Bedingungen, Beziehungen und sogar mehrere Tabellen auf einmal. Das DataSet-Objektmodell ist in Abbildung 9.7 zu sehen. Wenn Sie eine Verbindung zur Datenbank aufbauen, geben Sie ihr quasi eine Schachtel und befehlen dem Datenspeicher, diese mit etwas zu füllen. Sie können sie mit Datentabellen füllen, mit eigenen Daten von sonst wo oder mit anderen Daten. Ein DataSet ist eine solche Schachtel; die Datenbank übergibt Ihnen Kopien aller Daten, um die Sie bitten – und diese können Sie in der Schachtel herumtragen, ohne zugleich eine Verbindung zur Datenbank aufrechterhalten zu müssen. Abbildung 9.7 enthält auch ein DataTable-Objekt, das eine einzelne Datenbanktabelle darstellt. (Das DataSet verwaltet eine Auflistung dieser Tabellen im TablesCollection-Objekt.) Die DataTable ist eine vollständige Repräsentation der entsprechenden Tabelle inklusive aller Beziehungen und Schlüsselbedingungen. Sie enthält zwei weitere Auflistungen namens Rows und Columns, die jeweils die Daten und das Tabellenschema darstellen. Mit dem Objekt RelationsCollection können Sie über Tabellen je nach deren Beziehungen untereinander navigieren. Sie brauchen in Ihren SQL-Abfragen keine Joins und Unions mehr (die alte Methode zur Verknüpfung von Tabellen), denn ADO.NET macht das
323
ADO.NET einsetzen
DataSet ExtendedProperties RelationsCollection TablesCollection DataTable ChildRelations Columns
DataColumns
Constraints DefaultView ExtendedProperties ParentRelations PrimaryKeys Rows
DataRows
Abbildung 9.7: Das DataSet-Objektmodell enthält vollständige Mengen von Daten.
Navigieren viel einfacher. Die eigentlichen Beziehungen werden durch DataRelationObjekte dargestellt, die Informationen über die beiden zu verknüpfenden Tabellen enthalten, so etwa über die Primär- und Fremdschlüsselbeziehungen sowie die Namen der Beziehungen. Mit Hilfe des DataRelation-Objekts können Sie sogar Beziehungen hinzufügen. ADO.NET erzwingt zudem Schlüsseleinschränkungen; damit können Sie keine Tabelle derartig ändern, dass die Beziehung zu einer anderen Tabelle verletzt würde. Das ExtendedProperties-Objekt umfasst zusätzliche Informationen wie etwa Benutzernamen und Kennwörter. Kurzum, das DataSet ist ein vielseitiges Hilfsmittel. Es ist völlig gleich, wo die Daten herkommen oder wohin sie gehen; das DataSet ist völlig von jedem Datenspeicher abgetrennt. Daher können Sie DataSet als eine eigenständige Entität behandeln. Wie gesagt, kann DataSet mehrere DataTables enthalten. Da Sie bereits wissen, wie man DataTables erstellt, beherrschen Sie bereits die Methode, DataSets zu erstellen. Angenommen, Sie haben bereits eine Tabelle erzeugt und mit Zeilen und Spalten gefüllt, dann ist alles, was Sie noch tun müssen, sie dem DataSet hinzuzufügen: DataSet MyData = new DataSet("Meine Daten"); MyData.Tables.Add(Tabelle)
Diese Fähigkeit mag ja interessant sein, hat aber noch wenig praktischen Nutzen. Warum sollten Sie schließlich von Hand mehrere DataTables erzeugen, um sie dann in ein DataSet zu stecken? Diese Fähigkeit wird aber nützlicher, sobald Sie mit Datenbanken zu tun haben.
324
Das Objektmodell von ADO.NET
9.5
Das Objektmodell von ADO.NET
Nun ist es an der Zeit, mit einer Datenbank zu kommunizieren. In den folgenden Abschnitten betrachten Sie die Objekte im ADO.NET-Gerüst. Um mit Daten in einer Windows Forms-Anwendung zu interagieren, befolgen Sie diese fünf allgemeinen Schritte: 1. Erstellen Sie ein Objekt für eine Datenbankverbindung. 2. Öffnen Sie die Datenbankverbindung. 3. Füllen Sie ein DataSet mit den gewünschten Daten. 4. Richten Sie (optional) ein DataView-Objekt für die Datenanzeige ein. 5. Binden Sie ein Steuerelement an das DataSet oder die DataView. Da Sie schon gestern den fünften Schritt gründlich untersucht haben, konzentrieren Sie sich heute auf die ersten vier. Das erfolgt in den nächsten Abschnitten. Sie werden zwei verschiedene Objekttypen verwenden. Eine Menge ist ausschließlich für SQL Server reserviert (Sie können das den SQL-Provider nennen), und die andere ist für alles andere bestimmt (Sie können das den OLE DB-Provider nennen). Der OLE DB-Provider funktioniert auch mit dem SQL Server, doch der SQL-Provider nutzt einige spezielle Charakteristika von SQL Server, die ihn effizienter machen. Die zwei verschiedenen Objektmengen werden jeweils durch die Präfixe Sql oder OleDB gekennzeichnet. Meist können Sie zwischen den beiden wechseln, indem Sie einfach das Präfix austauschen. Alle Eigenschaften und Methoden sind fast identisch. In diesem Buch konzentrieren wir uns auf den SQL-Provider. Bevor wir Code schreiben, wollen wir einen Blick auf die ADO.NET-Steuerelemente werfen, die wir einsetzen: 쐽
SqlConnection-Objekt: Dieses Steuerelement stellt eine Datenbankverbindung dar.
쐽
SqlDataAdapter-Objekt: Dieses Mehrzweckwerkzeug dient der Kommunikation mit
einer Datenbank nach dem Aufbau einer Verbindung (etwa um Daten abzurufen, einzufügen oder zu aktualisieren). 쐽
SqlCommand-Objekt: Dieses Steuerelement stellt einen einzelnen von SqlAdapter
erzeugten Befehl dar. Das Objekt enthält Informationen über das SQL-Statement, das Sie auszuführen versuchen, und ist eng mit dem SqlAdapter verknüpft. 쐽
DataSet: Das kennen Sie schon: die unverbundene Darstellung einer Datenbank.
325
ADO.NET einsetzen
Die Schritte 1 und 2 sind mit dem SqlConnection-Objekt verbunden, während Schritt 3 mit den Objekten SqlDataAdapter und SqlCommand arbeitet. Sie werden alle diese Objekte gleich einsetzen.
Verbindung zur Datenbank aufnehmen und Daten abrufen Die Datenbankverbindung wird mit Hilfe des SqlConnection-Objekts gehandhabt (oder auch OleDBConnection, je nach Präfix). Alles was Sie dazu brauchen ist eine Verbindungszeichenfolge (Connection String) – eine Zeichenfolge, die .NET mitteilt, wo sich die Datenbank befindet, um welchen Datenbanktyp es sich handelt, welche Version das ist usw. Dieser Verbindungszeichenfolge ist jedoch allgemein gehalten, so dass Sie sie nach dem ersten Erstellen nicht noch einmal schreiben müssen. Je nach der verwendeten Datenbank ändert sich die Zeichenfolge. Das folgende Stück Code zeigt Beispielzeichenfolgen für SQL Server und Access: //SQL Server "Initial Catalog=TYWinforms;Data Source=localhost;" //Access, C# "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" + "C:\\winforms\\data\\TYWinforms.mdb;User ID=blah;Password=blah"; //Access, VB .NET "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" & _ "C:\\winforms\\data\\TYWinforms.mdb;User ID=blah;Password=blah";
In C# besitzt das Backslash-Zeichen (\) eine besondere Bedeutung, daher muss man ihm einen weiteren Backslash voranstellen, um diese besondere Bedeutung zu unterdrücken und literales Zeichen darzustellen. VB .NET hingegen kennt solche Einschränkungen nicht. Als Nächstes müssen Sie mit der Verbindungszeichenfolge ein SqlConnection-Objekt erzeugen: String strConn = "Initial Catalog=TYWinforms;Data Source=localhost;"; SqlConnection objConn = new SqlConnection(strConn);
Der nächste Schritt besteht im Füllen des DataSets mit den gewünschten Daten. Dafür müssen Sie ein paar SQL-Statements schreiben. Listing 9.1 zeigt eine einfache Anwendung, die diese Statements schreibt und die Daten daraufhin in einem DataGrid anzeigt.
326
Das Objektmodell von ADO.NET
Listing 9.1: Auf eine Datenbank (tblUsers) zugreifen 1: using System; 2: using System.Windows.Forms; 3: using System.Drawing; 4: using System.Data; 5: using System.Data.SqlClient; 6: 7: namespace TYWinforms.Day9 { 8: public class Listing91 : Form { 9: DataGrid dgData = new DataGrid(); 10: DataSet objDS = new DataSet(); 11: 12: public Listing91() { 13: String strConn = "Initial Catalog=TYWinforms;DataSource=localhost;User ID=sa"; 14: SqlConnection objConn = new SqlConnection(strConn); 15: 16: SqlDataAdapter objCmd = new SqlDataAdapter("SELECT * FROM tblUsers", objConn); 17: objCmd.Fill(objDS, "tblUsers"); 18: 19: dgData.DataSource = objDS; 20: dgData.DataMember = "tblUsers"; 21: dgData.Dock = DockStyle.Fill; 22: 23: this.Controls.Add(dgData); 24: } 25: 26: public static void Main() { 27: Application.Run(new Listing91()); 28: } 29: } 30: }
Sie sollten die Angabe localhost in Zeile 13 Ihrer eigenen Installation von SQL Server anpassen. Die Standardeinstellung localhost könnte zwar funktionieren, doch Sie sollten dies auf jeden Fall noch einmal im SQL Enterprise Manager prüfen. SQL Server lässt sich für folgende Arten von Anmeldekonten konfigurieren: Windows NT/2000-Konten (Windows-Authentifizierung), SQL Server-Konten (SQL Server-Authentifizierung) oder beide Kontenarten (gemischter Modus). "sa" ist das SQL Server-Konto für den Systemadministrator. Ist Ihr SQL Server jedoch auf Windows-Authentifizierung eingestellt, sollten Sie eine Verbin-
327
ADO.NET einsetzen
dungszeichenfolge wie die folgende verwenden, die dann Ihr Windows NT/ 2000-Konto (statt Ihr SQL Server-Konto) verwendet, um Sie zu authentifizieren: "Initial Catalog=TYWinforms;Data Source=localhost;Trusted_Connection=true;";
Beachten Sie zunächst die Integration der Namensräume System.Data und System.Data.SqlClient (Letzterer hieße System.Data.OleDBClient, falls Sie mit dem OLE DB-Provider arbeiten). Sie sind für die verschiedenen Datenverbindungsobjekte notwendig. In den Zeilen 13 und 14 erstellen Sie die Verbindungszeichenfolge und das SqlConnection-Objekt. In Zeile 16 wird Ihnen das SqlDataAdapter-Objekt vorgestellt. Es ist ein Mehrzweckwerkzeug für Datenbanken; es kann SQL-Statements ausführen, Daten zurückgeben und DataSets füllen. Sie verwenden es hier, um ein SELECT-Statement auszuführen und das DataGrid-Steuerelement zu füllen. Der Konstruktor für dieses Objekt übernimmt zwei Parameter: Der erste besteht aus dem auszuführenden SELECT-Statement (hier holen Sie einfach alle Datensätze aus der Tabelle tblUsers) und der zweite aus dem SqlConnection-Objekt, das den SqlDataAdapter darüber informiert, wo er nach den Daten suchen soll. (Mehr dazu gleich.) In Zeile 17 rufen Sie die Fill-Methode auf. Sie ist recht interessant, führt sie doch im Grunde das ihr zugeleitete SELECT-Statement aus, erstellt ein DataTable-Objekt, dessen Zeilen das Ergebnis des SELECT-Statements sind, und steckt diese DataTable in ein DataSet. Die zwei Parameter dieser Methode sind das DataSet, um dort die gelieferten Ergebnisse zu platzieren, und der Name der erstellten DataTable.
Abbildung 9.8: Ihr gebundenes DataGrid zeigt die Informationen aus der Datenbank an.
328
Das Objektmodell von ADO.NET
Der Rest des Codes bindet Daten an das DataGrid; es dürfte Ihnen noch von gestern bekannt sein. Sie setzen die Eigenschaft DataSource auf das neu gefüllte DataSet und die DataMember-Eigenschaft auf die im DataSet enthaltene DataTable. Wenn Sie das DataMember nicht näher bestimmen, zeigt das DataGrid die Objekte im DataSet und nicht die Daten an (anders ausgedrückt erscheint in der Tabelle »tblUsers« anstelle der Benutzer). Ihrer Anwendung Datenbankfunktionen hinzuzufügen, ist also recht einfach und erfordert lediglich fünf bis sechs Zeilen Code. Listing 9.1 produziert das in Abbildung 9.8 gezeigte Ergebnis.
Daten bearbeiten Mit dem SqlDataAdapter können Sie zudem Daten bearbeiten, wobei die Statements UPDATE, INSERT und DELETE verwendet werden können. Es stehen verschiedene Eigenschaften extra für diese Befehle bereit. Der SqlDataAdapter verfügt über die Eigenschaften UpdateCommand, InsertCommand, DeleteCommand und SelectCommand. SelectCommand wird im Konstruktor gefüllt (Zeile 16 von Listing 9.1) und ausgeführt, sobald die Fill-Methode aufgerufen wird. Die anderen Eigenschaften werden ausgeführt, wenn die Update-Methode des SqlDataAdapters aufgerufen wird, doch dabei gibt es einen Haken. Diese Statements kommen nur dann zur Ausführung, wenn sich bereits Daten im zugrunde liegenden DataSet geändert haben. Nehmen Sie an, Sie wollen sämtliche Vorkommen des Namens Christopher in der Benutzer-Datenbank in Chris ändern. Theoretisch könnten Sie eine UpdateCommand-Eigenschaft wie die folgende verwenden: objCmd.UpdateCommand = new SqlCommand("Update tblUsers SET VorName = 'Chris' WHERE VorName = 'Christopher'); objCmd.Update();
Sie erstellen ein neues SqlCommand-Objekt, übergeben ihm das passende UPDATE-Statement und rufen dann die Update-Methode zwecks Ausführung des Statements auf. Theoretisch sollte dieses Vorgehen zuerst das DataSet aktualisieren und dann die Änderungen zurück in die Datenbank schreiben. Es funktioniert aber nicht wie erwartet. Im Wesentlichen diktiert dieses Statement, auf welche Weise Änderungen im DataSet in die Datenbank zurückgeschrieben werden sollen. Die Update-Methode prüft das DataSet, um festzustellen, ob sich Werte geändert haben. Ist das nicht der Fall, passiert nichts. Andernfalls nimmt der SqlDataAdapter nur die geänderten Datensätze, führt das SQLStatement an ihnen aus und schreibt die Ergebnisse zurück in die Datenbank. Im obigen Codestück würde der vom Benutzer geänderte Christopher-Datensatz in der Datenbank zu Chris aktualisiert werden, ungeachtet des Wertes, den der Benutzer wirklich
329
ADO.NET einsetzen
geändert hat (weil es das UPDATE-Statement eben so festlegt). Würde der Benutzer einen anderen Datensatz ändern, der Christopher nicht enthielte, würde nichts passieren (weil die WHERE-Bedingung im UPDATE-Statement diese Datensätze ausschließt). Dieses Vorgehen ist mühselig. Will man Daten ändern, muss man sie erst von Hand im DataSet ändern und dann ein passendes SQL-Statement schreiben, das die Änderungen
zurück in die Datenbank schreibt. Das ist zeitraubend und obendrein fehlerträchtig. Zum Glück stellt ADO.NET ein Objekt namens SqlCommandBuilder zur Verfügung, das all diese SQL-Befehle automatisch für Sie erstellt. Wenn also der Benutzer im DataGrid ein Feld ändert, brauchen Sie sich nicht um die auszuführenden SQL-Befehle zu kümmern. Sie rufen nur die Update-Methode auf, fertig. Um dieses Objekt zu verwenden, fügen Sie die folgende Zeile nach Zeile 17 in Listing 9.1 ein: SqlCommandBuilder objBuilder = new SqlCommandBuilder(objCmd);
Diese Zeile erzeugt ein SqlCommandBuilder-Objekt für den bereits erstellten SqlDataAdapter. Das Objekt fungiert als Wachhund: Es beobachtet alles, was bei den Daten vor sich geht und erzeugt rasch ohne Ihr Zutun die passenden SQL-Befehle. Wir wollen die Daten in Listing 9.1 editierbar machen. Fügen Sie zuerst dem CurrentCellChanged-Ereignis des DataGrid-Steuerelements einen Delegaten hinzu, damit Sie wissen, wann der Benutzer mit dem Bearbeiten einer Zelle fertig ist: dgData.CurrentCellChanged += new EventHandler (this.UpdateData);
Dann fügen Sie den Ereignishandler hinzu, der einfach die Update-Methode des SqlDataAdapters aufruft: public void Update(Object Sender, EventArgs e) { objCmd.Update(objDS, "tblUsers"); }
Sofern Sie bereits den SqlCommandBuilder erstellt haben, schreibt dieses Statement alle Änderungen am DataSet (via DataGrid) in die Datenbank zurück, so dass sie zu dauerhaften Änderungen werden. Der erste Parameter ist das betreffende DataSet und der zweite die DataTable im DataSet, in der Änderungen erfolgen. Kompilieren Sie diese Anwendung neu und führen Sie sie aus. Jede von Ihnen vorgenommene Änderung wird nun automatisch in die Datenbank zurückgeschrieben. Um dies zu testen, ändern Sie ein paar Einträge und schließen dann die Anwendung. Sie können sie wieder öffnen, um zu sehen, ob die Änderungen permanent sind, oder Sie können die Datenbankanwendung (SQL Server oder Access) direkt öffnen, um die Änderungen zu sehen. Diese Einsatzweise des SqlCommandBuilder-Objekts funktioniert nur dann, wenn die zurückgegebenen Daten einen Primärschlüssel enthalten. Beim Aufbau der Tabelle tblUsers haben Sie den Primärschlüssel auf das Feld UserID gesetzt. Dementsprechend können Sie den Primärschlüssel per Programmcode unter Verwendung Ihres DataTable-Objekts setzen:
330
Das Objektmodell von ADO.NET
DataColumn[] arrKeys = {myTable.Columns["Spalte A"]}; myTable.PrimaryKey = arrKeys;
Die PrimaryKey-Eigenschaft der DataTable übernimmt ein Array von DataColumn-Objekten, weshalb Sie in diesem Codestück ein Array erstellen. Außerdem muss das SelectCommand zuerst mit einem SELECT-Statement gefüllt werden. Ihre Datenbankanwendung ist nunmehr voll funktionsfähig. Sie können Daten anzeigen, Benutzer können diese ändern und die Datenbank wird aktualisiert. Wir wollen uns ein weiteres Beispiel ansehen, das in Listing 9.2 abgedruckt ist. Diesmal verwenden Sie kein DataGrid-Steuerelement zur Datenanzeige, sondern vielmehr einige Textfelder, die nur einen Datensatz auf einmal anzeigen. Sie stellen Schaltflächen bereit, damit Benutzer durch die Datensätze navigieren können, sowie eine SPEICHERN-Schaltfläche, mit der die Benutzer Änderungen in die Datenbank schreiben können. Listing 9.2: Eine etwas komplexere Datenbankanwendung 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28:
using using using using using
System; System.Windows.Forms; System.Drawing; System.Data; System.Data.SqlClient;
namespace TYWinforms.Day9 { public class Listing92 : Form { DataSet objDS = new DataSet("tblUsers"); SqlDataAdapter objCmd; TextBox tbFirstName = new TextBox(); TextBox tbLastName = new TextBox(); Label lblFirstName = new Label(); Label lblLastName = new Label(); Button btNext = new Button(); Button btPrev = new Button(); Button btSave = new Button(); public Listing92() { lblFirstName.Location = new Point(10,10); lblFirstName.Text = "VorName: "; lblLastName.Location = new Point(10,35); lblLastName.Text = "NachName: "; tbFirstName.Location = new Point(75,10); tbLastName.Location = new Point(75,30); btNext.Text = "Nächster";
331
ADO.NET einsetzen
29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73:
332
btNext.Location = new Point(150,75); btNext.Click += new EventHandler(this.MoveNext); btPrev.Text = "Voriger"; btPrev.Location = new Point(10,75); btPrev.Click += new EventHandler(this.MovePrev); btSave.Text = "Speichern"; btSave.Location = new Point(75,125); btSave.Click += new EventHandler(this.Save); String strConn = "Initial Catalog=TYWinforms;DataSource=localhost;User ID=sa"; SqlConnection objConn = new SqlConnection(strConn); objCmd = new SqlDataAdapter("SELECT * FROM tblUsers", objConn); objCmd.Fill(objDS, "tblUsers"); SqlCommandBuilder objBuilder = new SqlCommandBuilder(objCmd); tbFirstName.DataBindings.Add("Text", objDS, "tblUsers.VorName"); tbLastName.DataBindings.Add("Text", objDS, "tblUsers.NachName"); this.Controls.Add(tbFirstName); this.Controls.Add(tbLastName); this.Controls.Add(lblFirstName); this.Controls.Add(lblLastName); this.Controls.Add(btNext); this.Controls.Add(btPrev); this.Controls.Add(btSave); } private void Save(Object Sender, EventArgs e) { this.BindingContext[objDS, "tblUsers"].EndCurrentEdit(); objCmd.Update(objDS, "tblUsers"); } private void MoveNext(Object Sender, EventArgs e) { this.BindingContext[objDS, "tblUsers"].Position += 1; } private void MovePrev(Object Sender, EventArgs e) { this.BindingContext[objDS, "tblUsers"].Position -= 1; } public static void Main() {
Das Objektmodell von ADO.NET
74: 75: 76: 77:
Application.Run(new Listing92()); } } }
Listing 9.2 weist eine Menge vielschichtiger Funktionen auf, daher wollen wir es lieber Zeile für Zeile durchgehen. In den Zeilen 1 bis 5 importieren Sie Namensräume. Die Zeilen 9 bis 18 richten die verschiedenen Objekte und Steuerelemente ein, die Sie in der Anwendung verwenden werden. Die Zeilen 21 bis 38 richten diverse Eigenschaften der Formularsteuerelemente ein und weisen ihnen ein paar Delegaten zu. Ab Zeile 40 erstellen Sie eine Datenbankverbindung mit Hilfe der Standardverbindungszeichenfolge. In den Zeilen 43 und 44 setzen Sie ein SqlDataAdapterObjekt ein, um Datensätze aus der Datenbank zu holen und damit ein DataSet zu füllen. In Zeile 45 erzeugen Sie Ihr SqlCommandBuilder-Objekt, um sicherzustellen, dass SQL-Befehle automatisch erzeugt werden, sobald Änderungen in der Tabelle erfolgen. In den Zeilen 47 und 48 binden Sie als Nächstes Elemente der gelieferten Daten mit Hilfe ihrer DataBindings-Eigenschaften an die TextBox-Steuerelemente. Denken Sie daran, dass die drei Parameter für die Add-Methode nacheinander die Eigenschaft der anzubindenden TextBox, das Datenquellenobjekt und das anzubindende Feld in der Datenquelle sind. Die Werte tblUsers.VorName und tblUsers.NachName geben die Spalten für Vor- und Nachnamen in tblUsers an. Würden Sie sie an ein DataGrid binden, könnten Sie einfach tblUsers als Feldname verwenden, doch bei TextBox-Steuerelementen müssen Sie etwas genauer sein. Schließlich wollen Sie nicht mehr als eine Spalte an eine TextBox binden. Gehen wir zu den Ereignishandlern über. Sowohl die MoveNext- und MovePrevMethoden ändern die Position-Eigenschaft des BindingContext-Objekts, damit man in den Datensätzen vor- und zurückblättern kann (Zeilen 66 und 70). Beachten Sie, dass Sie den BindingContext der Form bearbeiten und nicht jede gebundene TextBox einzeln. Wenn Sie das Formular manipulieren, werden alle gebundenen Steuerelemente im Formular ebenfalls modifiziert. Bevor wir die Save-Methode in Zeile 59 untersuchen, schauen wir uns an, was mit der Anwendung passiert. Wenn Sie sie ausführen, sehen Sie Vor- und Nachnamen des ersten Datensatzes in der Datenbank, wie in Abbildung 9.9 zu sehen. Sie können auf die Schaltfläche NÄCHSTER klicken, um zum nächsten Datensatz zu blättern, so dass sich der Text im Textfeld ändert. Die Schaltfläche VORIGER blättert einen Datensatz zurück.
333
ADO.NET einsetzen
Abbildung 9.9: Sie sehen den Vor- und Nachnamen, wenn Sie Daten an die TextBox-Steuerelemente binden.
Wenn Sie Elemente bearbeiten, geht etwas Seltsames im Hintergrund vor sich. Ändert man den Wert in einem der Textfelder, aktualisiert es theoretisch sofort das DataSet; dann können Sie die Update-Methode von SqlDataAdapter aufrufen, um die Änderungen in die Datenbank zurückzuschreiben. Tatsächlich werden die Änderungen am DataSet erst dann vorgenommen, wenn sie BindingContext bestätigt hat. Dies kann an mehreren Stellen erfolgen (dazu gehört das Ändern der Position-Eigenschaft). Jetzt haben wir ein Problem: Angenommen, der Benutzer ändert den Wert des Vornamens im Textfeld und klickt dann auf die Schaltfläche SPEICHERN. Weil der BindingContext noch keine Gelegenheit hatte, die Änderungen abzusegnen, hat sich das DataSet noch nicht geändert – auch die Datenbank wird sich nicht ändern. Sie brauchen eine Möglichkeit, wie Sie BindingContext zwingen können, die Änderungen zu bestätigen, damit das Speichern der Änderungen stattfinden kann. Die EndCurrentEdit-Methode aus Zeile 60 macht genau dies. Sie schreibt alle anstehenden Änderungen in das DataSet, ohne die Position-Eigenschaft ändern zu müssen. Danach können Sie einfach die Update-Methode aufrufen, wie in Zeile 62 zu sehen. Jetzt ist Ihre Anwendung vollständig.
9.6
Andere ADO.NET-Objekte
Bis hierher haben Sie die Objekte SqlDataAdapter und DataSet verwendet, um Datenquellen zu verarbeiten. Obwohl diese Objekte leistungsfähig und notwendig sind, erweisen sie sich für manche Zwecke als weniger geeignet. Das DataSet beispielsweise kann unter Umständen ein recht sperriges Objekt sein, da es ja eine komplette Mini-Datenbank an Informationen zu pflegen hat, inklusive Tabellenstruk-
334
Andere ADO.NET-Objekte
turen, Einschränkungen, Indizes, Beziehungen usw. Zuweilen benötigt man diese Extrafunktionen gar nicht. Um diesen Missstand zu beheben, führt ADO.NET die Objekte SqlDataReader und OleDbDataReader ein. Diese Objekte erhalten nicht ihre eigene Kopie der Datenbank aufrecht, sondern holen vielmehr einen Datensatz nach dem anderen – deshalb erfordern sie eine ständige Verbindung zur Datenbank (anders als der unverbundene Datenspeicher des DataSets). Die DataReader-Objekte weisen daher einen eingeschränkten Funktionsumfang auf; man kann zum Beispiel nicht laufend zwischen Datensätzen hin und her springen, ohne für das ständige Neuholen von Daten einen Preis an Performanz zu zahlen. Um ein DataReader-Objekt nutzen zu können, müssen Sie zuerst die Objekte SqlCommand und OleDbCommand einsetzen. Beide stellen einen an einer Datenbank ausgeführten SQLBefehl dar und können ein Ergebnis liefern oder auch nicht. Sie haben sie bereits eingesetzt. Wenn Sie den SqlDataAdapter-Eigenschaften für das Aktualisieren, Einfügen, Löschen oder Auswählen etwas zuweisen, dann erzeugt .NET für Sie verschiedene SqlCommand-Objekte dafür. Es sind diese hinter den Kulissen agierenden Objekte, die eigentlich die Datenbank abfragen und Ergebnisse liefern; der SqlDataAdapter ist lediglich der Nutznießer ihrer harten Arbeit. Ein SqlCommand-Objekt lässt sich auf verschiedene Art und Weise erstellen. Sie können ihm je nach Bedarf ein SQL-Statement zur Ausführung übergeben, ein SQL-Statement und ein SqlConnection-Objekt oder auch rein gar nichts. Hier ist ein Beispiel: SqlCommand objCmd = new SqlCommand("SELECT *FROM tblUsers"); //oder SqlCommand objCmd = new SqlCommand("SELECT *FROM tblUsers", objConn); //oder SqlCommand objCmd = new SqlCommand();
Stellen Sie nicht einen oder mehrere dieser Werte während der Instantiierung des Objekts bereit, so können Sie dies mit Hilfe der Eigenschaften CommandText und Connection durchführen. Sobald Sie Ihr SqlCommand-Objekt haben, können Sie einen SQL-Befehl auf unterschiedliche Weise ausführen. Wenn Ihre Abfrage keine Daten liefert (etwa bei einem DELETE-Statement), dann können Sie die Methode ExecuteNonQuery verwenden: SqlCommand objCmd = new SqlCommand("DELETE FROM tblUsers ", objConn); objCmd.ExecuteNonQuery();
Liefert Ihre Abfrage einen Einzelwert, lässt sich die ExecuteScalar-Methode einsetzen. Sie holt die erste Spalte der ersten Zeile in den gelieferten Ergebnissen – so wie in diesem Beispiel: SqlCommand objCmd = new SqlCommand("SELECT COUNT(*)FROM tblUsers", objConn); int intCount = (int)objCmd.ExecuteScalar();
Wollen Sie Ihre Daten als XML geliefert bekommen, können Sie die ExecuteXmlReaderMethode einsetzen, die ein XmlReader-Objekt zurückgibt.
335
ADO.NET einsetzen
Wenn Sie schließlich Ihre Daten mit Hilfe eines SqlDataReader-Objekts verarbeiten wollen, steht dafür die ExecuteReader-Methode bereit: SqlDataReader objReader; SqlCommand objCmd = new SqlCommand("SELECT *FROM tblUsers ", objConn); objReader = objCmd.ExecuteReader();
Die einfache Anwendung in Listing 9.3 verwendet SqlDataReader, um die Ergebnisse aus einer Datenbank anzuzeigen. Listing 9.3: Die Verwendung eines SqlDataReaders 1: using System; 2: using System.Windows.Forms; 3: using System.Drawing; 4: using System.Data; 5: using System.Data.SqlClient; 6: 7: namespace TYWinforms.Day9 { 8: public class Listing93 : Form { 9: Label lblData = new Label(); 10: 11: public Listing93() { 12: lblData.Dock = DockStyle.Fill; 13: 14: String strConn = "Initial Catalog=TYWinforms;DataSource=localhost;User ID=sa"; 15: SqlConnection objConn = new SqlConnection(strConn); 16: objConn.Open(); 17: 18: SqlDataReader objReader; 19: SqlCommand objCmd = new SqlCommand("SELECT UserID, FirstName FROM tblUsers", objConn); 20: objReader = objCmd.ExecuteReader(); 21: 22: while (objReader.Read()) { 23: lblData.Text += objReader.GetInt32(0) + ": " + objReader.GetString(1) + "\n"; 24: } 25: 26: objReader.Close(); 27: objConn.Close(); 28: 29: this.Controls.Add(lblData); 30: } 31:
336
Andere ADO.NET-Objekte
32: 33: 34: 35: 36:
public static void Main() { Application.Run(new Listing93()); } } }
In den Zeilen 14 und 15 legen Sie ganz normal mit der Erzeugung eines SqlConnection-Objekts los. In Zeile 16 müssen Sie die Verbindung mit Hilfe der Open-Methode öffnen. Verwenden Sie den SqlDataAdapter, ist dieser Schritt nicht erforderlich, doch der SqlDataReader benötigt ja eine offene Verbindung, um arbeiten zu können. Die Zeilen 18 bis 20 erstellen ein SqlDataReaderObjekt sowie ein SqlCommand-Objekt mit einer SQL-Abfrage, bevor sie die ExecuteReader-Methode aufrufen, um die Abfrageergebnisse im Reader abzulegen. Die Read-Methode von SqlDataReader springt so lange zum nächsten Datensatz in der Ergebnismenge, bis deren Ende erreicht ist, woraufhin sie dann false zurückgibt. Daher verwendet man in Zeile 22 eine while-Schleife, um so alle Datensätze im Reader zu durchlaufen. Sobald das Ende der Datensätze erreicht ist, endet die while-Schleife. Das SqlDataReader-Objekt verfügt über eine Reihe von Get-Methoden, die die Werte aus der Ergebnismenge holen. Welche Methode sie genau benutzen, hängt jeweils vom zu erwartenden Datentyp ab. So liefert beispielsweise die GetString-Methode einen Zeichenfolgenwert und GetInt32 einen Integerwert. Diese Methoden übernehmen alle einen einzelnen Parameter, der angibt, welches Feld (bzw. welche Spalte) des aktuellen Datensatzes geliefert werden soll. In Zeile 23 holen Sie die ersten und zweiten Spaltenwerte jedes Datensatzes, wobei es sich um UserID und VorName aus der Tabelle tblUsers handelt. Nachdem alle Datensätze durchlaufen wurden, sorgen Sie für das Schließen des Readers und der SqlConnection-Objekte, indem Sie deren jeweilige Close-Methode verwenden, wie in den Zeilen 26 und 27 zu sehen. Listing 9.3 produziert das in Abbildung 9.10 gezeigte Ergebnis.
Abbildung 9.10: Sie können mit dem SqlReader-Objekt durch Datensätze blättern.
337
ADO.NET einsetzen
Ich empfehle
Bitte beachten Sie
Verwenden Sie einen SqlDataReader, wenn Sie Setzen Sie den SqlDataReader nicht ein, wenn Daten nur anzeigen, aber nicht bearbeiten wollen. Sie dem Benutzer gestatten wollen Daten zu bearbeiten oder wenn Sie komplexe Operationen mit diesen Daten ausführen müssen (wie etwa das Bearbeiten der Tabellenbeziehungen). Verwenden Sie dann statt dessen SqlDataAdapter oder DataSet.
Parametrisierte Abfragen und gespeicherte Prozeduren Eine gespeicherte Prozedur ist eine Reihe von einem oder mehreren SQL-Statements, die außerhalb einer Anwendung vordefiniert wird. In der Datenbank hat sie ihren Platz neben den Tabellen. Sie werden für Ihre Datenbankinteraktionen häufig gespeicherte Prozeduren einsetzen. Sie sind schneller als normale SQL-Statements, denn sie werden kompiliert. Sie tragen zudem dazu bei, dass Ihre Anwendung leichter zu pflegen ist: Wenn sich Ihre Datenanforderungen ändern sollten, gestaltet sich das Aktualisieren der gespeicherten Prozeduren weitaus einfacher, als die Abfragen in Ihrer Anwendung umzuschreiben und sie neu zu kompilieren. In Access werden gespeicherte Prozeduren als Abfragen bezeichnet; sie verwenden eine etwas andere Syntax als die in SQL Server. In diesem Buch beschränken wir uns auf gespeicherte Prozeduren in SQL Server. Um in SQL Server eine gespeicherte Prozedur zu erstellen, öffnen Sie den Enterprise Manager und expandieren die Liste der Datenbanken so weit, bis Sie den Abschnitt GESPEICHERTE PROZEDUREN unterhalb der TYWinforms-Datenbank sehen. Rechtsklicken Sie auf diesen Abschnitt und wählen Sie NEUE GESPEICHERTE PROZEDUR aus. Für unsere Zwecke wollen wir alles etwas einfacher halten. Tippen Sie den folgenden Code in das nun erscheinende Fenster: CREATE PROCEDURE qryGetUser (@intUserID int ) AS SELECT *FROM tblUsers WHERE UserID =@intUserID
Ihr Fenster sollte so aussehen wie das in Abbildung 9.11. Diese Abfrage liefert die kompletten Informationen über einen bestimmten Benutzer, wie durch die Verwendung der Benutzer-ID deutlich gemacht wird. Elegant an dieser Abfrage ist, dass die Benutzer-ID variieren kann: Die Abfrage akzeptiert einen Parameter namens @intUserID, den Ihre Anwendung bereitstellen muss, um die richtigen Ergebnisse zu erhalten. Sehen wir uns an, wie man diesen Parameter bereitstellen kann.
338
Andere ADO.NET-Objekte
Abbildung 9.11: Benutzen Sie dieses Fenster, um in SQL Server eine gespeicherte Prozedur zu erstellen.
Alle Parameter müssen Teil eines SqlCommand-Objekts sein (das bedeutet, dass Sie entweder einen SqlDataAdapter und ein DataSet oder einen SqlDataReader einsetzen, um Ihre Daten abzurufen). Mit Hilfe der oben erstellten gespeicherten Prozedur qryGetUser erzeugt das folgende Stück Code ein SqlCommand-Objekt mit den notwendigen Parametern: SqlCommand objQuery = new SqlCommand("qryGetUser ", objConn); objQuery.CommandType = CommandType.StoredProcedure; objQuery.Parameters.Add("@intUserID ", SqlDbType.Int, 4).Value = 1;
Die Deklaration des SqlCommand-Objekts beginnt wie üblich, mit einer Ausnahme: Statt der auszuführenden SQL-Anweisung übergeben Sie dem Objekt den Namen der gespeicherten Prozedur. In der zweiten Zeile müssen Sie die CommandType-Eigenschaft auf den StoredProcedure-Wert der CommandType-Aufzählung setzen. Andere Werte in dieser Aufzählung sind Text (der Standardwert, der bei Übergabe einer SQL-Anweisung verwendet wird) und TableDirect, welcher einfach die Struktur einer Tabelle liefert (dieser letzte Wert funktioniert nur im Zusammenspiel mit einem OLE DB-Provider). Zum Schluss fügen Sie der Parameters-Eigenschaft des SqlCommand-Objekts einen neuen Parameter hinzu. Der erste Parameter für die Add-Methode ist der Name des erwarteten Parameters in der gespeicherten Prozedur; er muss den gleichen Namen tragen wie in der gespeicherten Prozedur. Der zweite Parameter gibt den Datentyp des Parameters an; Tabelle 9.4 führt die möglichen Werte der SqlDbType-Aufzählung auf. Der dritte Wert gibt die Größe an (dieser Wert wurde beim Erstellen der Tabelle gesetzt; er braucht nicht exakt genau zu sein). Mit Hilfe der Value-Eigenschaft setzen Sie dann den Wert fest, den der Parameter der gespeicherten Prozedur verwenden soll. In unserem Fall läuft es darauf hinaus, dass Sie die Informationen für den Benutzer mit der Benutzer-ID 1 zurückerhalten.
339
ADO.NET einsetzen
SqlDbType-Wert
Entsprechender .NET-Typ
BigInt
Int64
Binary
Array des Typs Byte
Bit
Boolean
Char
String
DateTime
DateTime
Decimal
Decimal
Float
Double
Image
Array des Typs Byte
Int
Int32
Money
Decimal
NChar
String
NText
String
NVarChar
String
Real
Single
SmallDateTime
DateTime
SmallInt
Int16
SmallMoney
Decimal
Text
String
Timestamp
DateTime
TinyInt
Byte
UniqueIdentifier
Guid
VarBinary
Array des Typs Byte
VarChar
String
Variant
Object
Tabelle 9.4: Werte der SqlDbType-Aufzählung
340
Andere ADO.NET-Objekte
Da Sie nun ein SqlCommand-Objekt inklusive Parametern haben, können Sie damit tun, was Sie wollen. Um beispielsweise Daten für einen SqlDataReader zurückzuerhalten, verwenden Sie folgenden Code: SqlDataReader objReader; objReader = objQuery.ExecuteReader();
Wenn Sie hingegen ein DataSet füllen wollen, setzen Sie folgenden Code ein: DataSet objDS = new DataSet(); SqlDataAdapter objCmd = new SqlDataAdapter(); objCmd.SelectCommand = objQuery; objCmd.Fill(objDS, "tblUsers ");
Im zweiten Codestück weisen Sie das SqlCommand-Objekt der SelectCommand-Eigenschaft des SqlDataAdapters zu. Je nachdem, was die gespeicherte Prozedur eigentlich bewirkt, hätten Sie statt dessen auch die UpdateCommand-, InsertCommand- oder DeleteCommand-Eigenschaften angeben können. Es gibt eine Alternative: Bei Verwendung des SqlDataAdapters brauchen Sie nicht zwingend ein separates SqlCommand-Objekt anzulegen. Sie können auf den notwendigen Befehl auch aus der SelectCommand-Eigenschaft heraus verweisen: SqlDataAdapter objCmd = new SqlDataAdapter("qryGetUser ",objConn); objCmd.SelectCommand.CommandType = CommandType.StoredProcedure; objCmd.SelectCommand.Parameters.Add("@intUserID ", SqlDbType.Int, 4).Value = 1;
Das Objekt DataView Eine DataView ist die benutzerdefinierte Ansicht einer DataTable. Sie können also eine vorhandene mit Daten gefüllte DataTable auswählen und sie mit Filtern Ihren Wünschen anpassen. Dafür brauchen Sie also weder Ihre DataTable zu ändern noch Ihre Datenbank laufend neu abzufragen: Es ist einfach eine gefilterte Ansicht. Sie können die DataView an die Steuerelemente in Windows Forms binden. Eine DataView erstellen Sie ganz einfach, indem Sie ihr die anzupassende Tabelle mitteilen: DataView myView = new DataView (objDS.Tables["tblUsers"]);
Sie können die RowFilters-Eigenschaft für das Filtern der Ergebnisse einsetzen; im Grunde funktioniert diese Eigenschaft wie die WHERE-Klausel in einem SQL-Statement. Das folgende Statement würde zum Beispiel nur diejenigen Datensätze zur Anzeige bringen, in denen das VorName-Feld das Wort »Christopher« enthält. myView.RowFilter = "VorName = 'Christopher'";
341
ADO.NET einsetzen
Die Sort-Eigenschaft der DataView, mit der Sie die Sortierreihenfolge der Daten ändern können, folgt der gleichen Syntax wie ORDER BY in SQL-Befehlen: myView.Sort = "VorName ASC, NachName DESC";
Die RowStateFilter-Eigenschaft filtert Datensätze gemäß ihrem aktuellen Status, beispielsweise, ob sie bearbeitet wurden, unverändert geblieben sind oder hinzugefügt wurden: myView.RowStateFilter = DataViewRowState.Unchanged;
Diese Werte lassen sich mit Hilfe des Operators | verknüpfen (OR in VB .NET). Die möglichen Werte in der DataViewRowState-Aufzählung lauten wie folgt: 쐽
Added: Gibt an, dass die Zeile neu ist.
쐽
CurrentRows: Schließt unveränderte, neue und bearbeitete Zeilen mit ein.
쐽
Deleted: Gibt eine gelöschte Zeile an.
쐽
ModifiedCurrent: Gibt den aktuellen Wert aller Zeilen an, nachdem eine beliebige
Zeile geändert wurde. 쐽
ModifiedOriginal: Gibt den ursprünglichen Wert aller Zeilen an, nachdem eine beliebige Zeile geändert wurde.
쐽
None: Gibt Zeilen an, die keinen Status haben.
쐽
OriginalRows: Gibt alle ursprünglichen Zeilen an, darunter auch die unveränderten
und gelöschten Zeilen. 쐽
Unchanged: Gibt eine unveränderte Zeile an.
Sie können die Art und Weise steuern, wie der Benutzer mit der DataView interagiert, indem Sie die Eigenschaften AllowDelete, AllowEdit und AllowNew verwenden. Da die DataView als Operationsbasis ein DataTable-Objekt benötigt, können Sie eine DataView nicht zusammen mit einem SqlDataReader-Objekt einsetzen. Nutzen Sie statt dessen die Objekte DataSet und SqlDataAdapter.
9.7
XML im Zusammenhang mit ADO.NET
ADO.NET ist eng mit XML verzahnt. ADO.NET verwendet XML in sämtlichen Transaktionen, so dass es vielseitiger verwendbar und effizienter wird. Sie können folglich leicht von ADO.NETs Kernobjekt, dem DataSet, zu XML gelangen und umgekehrt. Die meisten der XML-bezogenen Funktionen, die Sie mit ADO.NET ausführen wollen, lassen sich mit nur zwei Methoden bewerkstelligen: ReadXml und GetXml. Die erste lädt
342
XML im Zusammenhang mit ADO.NET
XML aus jedem beliebigen XML-basierten Objekt (etwa einer Textdatei, aus einem Datenstrom, einem XML-Dokument usw.) und legt die Ergebnisse in einem DataSet ab. Die zweite Methode nimmt umgekehrt Daten aus einem DataSet und liefert eine Zeichenfolge, die den XML-Code enthält, der die Daten darstellt. Wir wollen uns ein konkretes Beispiel ansehen. Listing 9.4 zeigt das XML-Codestück vom Kapitelanfang: Listing 9.4: XML-Beispieldaten 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12:
<Users> <User> Chris Payne <UserID>1 27. Juni <User> Eva Payne <UserID>2 15. Juli
Speichern Sie Listing 9.4 als Sample.xml im Ordner C:\winforms\data. Dieser XML-Code präsentiert eine Datenbank namens Users mit einer Tabelle namens User. Diese Tabelle umfasst drei Spalten: Name, UserID und Geburtsdatum. Als Nächstes zeigt Listing 9.5 eine einfache Anwendung, die die XML-Daten lädt und bindet. Listing 9.5: XML-Daten mit ADO.NET binden 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15:
using using using using using
System; System.Windows.Forms; System.Drawing; System.Data; System.Data.SqlClient;
namespace TYWinforms.Day9 { public class Listing95 : Form { DataSet objDS = new DataSet(); TextBox tbName = new TextBox(); TextBox tbGeburtsdatum = new TextBox(); Label lblName = new Label(); Label lblGeburtsdatum = new Label();9
343
ADO.NET einsetzen
16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39:
public Listing95(){ lblName.Location = new Point(10,10); lblName.Text = "Name: "; lblGeburtsdatum.Location = new Point(10,35); lblGeburtsdatum.Text = " Geburtsdatum: "; tbName.Location = new Point(75,10); tbGeburtsdatum.Location = new Point(75,30); objDS.ReadXml("c:\\winforms\\data\\sample.xml"); tbName.DataBindings.Add("Text ", objDS, "User.Name "); tbGeburtsdatum.DataBindings.Add("Text ", objDS, "User.Geburtsdatum "); this.Controls.Add(tbName); this.Controls.Add(tbGeburtsdatum); this.Controls.Add(lblName); this.Controls.Add(lblGeburtsdatum); } public static void Main(){ Application.Run(new Listing95()); } } }
Der Großteil dieses Codes initialisiert die Benutzeroberfläche. Interessant wird es erst ab Zeile 24, in der Sie die ReadXml-Methode zum Laden einer Datei einsetzen. Der Parameter zeigt den Pfad zur fraglichen Datei an. In den Zeilen 26 und 27 binden Sie ganz normal die zwei TextBox-Steuerelemente. Sorgen Sie jedoch dafür, dass der dritte Parameter die Struktur des XML-Dokuments richtig widerspiegelt. Listing 9.5 führt zu dem in Abbildung 9.12 gezeigten Ergebnis. Was passiert nun, wenn die Daten aktualisiert werden? Kann man die Update-Methode aufrufen, um die Änderungen ins XML-Dokument zurückzuschreiben? Leider nicht. Die einzige Möglichkeit, die XML-Quelle zu aktualisieren, besteht darin, explizit eine XML-Datei zu schreiben, doch dieser Vorgang ist zum Glück einfach. Zuerst erstellen Sie ein Objekt XmlDataDocument, um den XML-Code aus dem DataSet zu holen. Dann setzen Sie dessen Save-Methode ein, um die Datei zu schreiben: using System.Xml; ... XmlDataDocument objXml = new XmlDataDocument(objDS); objXml.Save("c:\\winforms \\data \\sample2.xml ");
344
Zusammenfassung
Abbildung 9.12: Sie können die Anwendung in Listing 9.5 verwenden, um XML-Daten anzubinden.
Ein XmlDataDocument-Objekt ist in der XML-Welt das Äquivalent zum SqlDataAdapter. Dieses Objekt ist mit dem DataSet verknüpft, so dass Sie auf einfache Weise vom einen zum anderen gelangen. Die erste Zeile im obigen Codestück instantiiert ein neues XMLDataDocument-Objekt und übergibt ihm das fragliche DataSet. Die Save-Methode schreibt die XML-Daten zum angegebenen Pfad. Das XMLDataDocument weist noch einige weitere nützliche Mitglieder auf, die wir hier aber nicht erörtern werden. Eine komplette Referenz dazu finden Sie in der Dokumentation des .NET Framework SDK.
9.8
Zusammenfassung
Heute haben Sie eine Menge über Datenbanken gelernt: von ihrer Erstellung in Access und SQL Server über den Einsatz parametrisierter gespeicherter Prozeduren bis hin zum Einsatz von XML. Mit ADO.NET kann man auf fast jedes Datenbanksystem mit Hilfe einer einfachen Verbindungszeichenfolge zugreifen, die angibt, wo sich die Daten befinden, welche Erlaubnis für den Zugriff nötig ist usw. Sie haben von SQL-Statements wie SELECT, INSERT, DELETE und UPDATE erfahren. Mit Hilfe dieser Statements können Sie Daten aus jedem Datenbanksystem, das sie unterstützt, holen und bearbeiten. Jedes Statement befolgt eine ähnliche Syntax, so dass man sie leicht alle erlernen kann, sobald man eines kennt. DataSet ist das zentrale Objekt in ADO.NET. Es stellt eine vollständige Mini-Datenbank dar. Wird das DataSet mit den Objekten SqlDataAdapter und OleDbDataAdapter sowie mit
Datenbindung gekoppelt, lassen sich dem Benutzer Daten auf einfache Weise präsentieren. Sobald man die Update-Methode des SqlDataAdapters mit dem SqlCommandBuilderObjekt verknüpft, kann man Änderungen in die Datenbank zurückschreiben, um sie dauerhaft zu speichern.
345
ADO.NET einsetzen
Zusätzlich zum DataSet gibt es die DataReader-Objekte, die zwar effizienter einzusetzen sind, aber einen geringeren Leistungsumfang besitzen. Diese Objekte lassen sich mit Hilfe der ExecuteReader-Methode des SqlCommand-Objekts füllen. Das DataView-Objekt erlaubt Ihnen, die Anzeige Ihrer DataTable-Objekte wirkungsvoll anzupassen, wodurch Ihnen erspart bleibt, mühevoll immer und immer wieder SQL-Statements ausführen zu müssen, sobald Sie Daten filtern wollen. Zum Schluss lernten Sie, wie ADO.NET und das DataSet mit Hilfe der Methoden ReadXml und GetXml auch XML-Daten laden und bearbeiten können. Diese Daten lassen sich genau wie andere Daten binden, Änderungen lassen sich mit Hilfe der Save-Methode des XMLDataDocument-Objekts in die XML-Dateien zurückschreiben.
9.9 F
Kann ich das ADO-Recordset weiterhin mit ADO.NET und dem DataSet verwenden? A
F
Fragen und Antworten Ja. Mit Hilfe der Fill-Methode können Sie ein DataSet mit Daten aus einem Recordset füllen, doch auf Grund von Beschränkungen in ADO geht das nicht auch umgekehrt. Dieser Vorgang erfordert, die ADO-Typbibliotheken in das .NET Framework zu importieren. (Mehr dazu an Tag 14, wenn es um den Einsatz von ActiveX geht.)
Warum sind gespeicherte Prozeduren effizienter als normale SQL-Statements? Sollte ich also statt dessen immer gespeicherte Prozeduren verwenden? A
Gespeicherte Prozeduren sind aus zwei Gründen effizienter. Zum einen sind sie kompiliert und kompilierte Anwendungen sind stets schneller als unkompilierte. Zweitens hat SQL Server einen so genannten »Ausführungsplan«. Wurde eine gespeicherte Prozedur bereits einmal ausgeführt, weiß SQL Server genau, wo er die Daten suchen soll und wie er sie am besten holt. Dieser Ausführungsplan steigert die Ausführungsgeschwindigkeit einer Abfrage enorm. Der Haken dabei ist nur, dass dieser Ausführungsplan bei normalen Abfragen nicht gespeichert wird und SQL Server ihn bei der nächsten Ausführung der Abfrage neu erstellen muss. Die Antwort auf die Frage, ob man gespeicherte Prozeduren stets normalen Abfragen vorziehen sollte, ist umstritten. Die Wahl bleibt Ihnen überlassen. Doch zum Glück gibt es ein paar Richtlinien, um Ihnen dabei zu helfen. Im Allgemeinen verwenden Sie eine gespeicherte Prozedur, wenn 왘
346
Ihre Abfrage über ein einfaches SELECT-Statement hinausgeht. (Je komplizierter ein SQL-Statement, desto höher sein Ressourcenbedarf, besonders dann, wenn kein Ausführungsplan zur Verfügung steht.)
Workshop
F
왘
Sie wissen, dass sich die Abfrage künftig ändern könnte, da sich das Datenmodell ändert.
왘
die Abfrage häufig ausgeführt wird, also häufiger als ein- oder zweimal pro Anwendungsnutzung.
왘
Sie mit einer umfangreichen Datenmenge hantieren müssen.
Kann ich die Daten in einem SqlDataReader binden? A
Leider nicht. Weil der SqlDataReader nur jeweils einen Datensatz aus einer Datenquelle holen kann, können Sie ihn nicht binden. Aus diesem Grund sollten Sie beim DataSet bleiben, wenn Sie den Benutzer Daten bearbeiten lassen wollen.
9.10 Workshop Dieser Workshop soll Ihnen helfen, sich besser an die heute behandelten Begriffe zu erinnern. Es hilft Ihnen sehr, die Antworten vollständig zu verstehen, bevor Sie weiterlesen. Die Antworten zu den Quizfragen und den Übungen finden Sie in Anhang A.
Quiz 1. Schreiben Sie ein SELECT-Statement, das aus der tblUsers-Tabelle nur solche Datensätze holt, in denen der Wert für das Feld UserID zwischen 5 und 10 liegt. 2. Welche Parameter fordert das SqlCommandBuilder-Objekt für seinen Konstruktor? 3. Ein SqlCommandBuilder erzeugt SQL-Befehle nur dann automatisch, wenn ein Primärschlüssel existiert. Warum? 4. Wahr oder falsch? Meist können Sie die Vorsilbe Sql durch OleDb ersetzen, um die Objekte im OLE DB-Provider nutzen zu können. 5. Ist es ausreichend, die DataSource-Eigenschaft eines DataGrid-Steuerelements auf ein gefülltes DataSet zu setzen? 6. Schreiben Sie für ein gegebenes SqlCommand-Objekt namens objCmd ein Statement, das einen Parameter namens @Geburtsdatum mit einem Wert von 1/7/01 hinzufügt. 7. Nennen Sie die Methode, die veranlasst, dass Änderungen sofort in ein DataSet geschrieben werden.
347
ADO.NET einsetzen
Übung Erstellen Sie eine Anwendung, mit der der Benutzer SQL-Statements in ein Textfeld eingeben kann. Sollte das Statement Ergebnisse liefern, legen Sie sie in einem DataSet ab und gestatten Sie dessen Bearbeitung. Kümmern Sie sich nicht darum, ob die Abfrage im richtigen Format eingegeben wird; Fehlerprüfungen werden erst an Tag 21 durchgenommen.
348
MDI-Anwendungen erstellen
0 1
MDI-Anwendungen erstellen
Bis jetzt haben Sie lediglich Anwendungen mit einer Einzeldokumentschnittstelle (Single Document Interface, SDI) erstellt: Sie verfügen nur über eine Schnittstelle, mit der der Benutzer zusammenarbeitet. Bei einer Mehrfachdokumentschnittstelle (Multiple Document Interface, MDI) kann der Benutzer mit mehreren Ausgaben der gleichen Schnittstelle gleichzeitig arbeiten, so dass sich die Produktivität erhöht. Heutzutage sind viele Anwendungen MDI-Applikationen. In der heutigen Lektion lernen Sie, wie Sie MDI-Anwendungen erstellen. Die Grundlagen zu erwerben ist einfach, doch die Handhabung all dieser Schnittstellen zugleich erfordert ein wenig Feingefühl. Heute lernen Sie, 쐽
was MDI ist und wie Ihnen dieses Konzept hilft,
쐽
wie Sie eine Excel-ähnliche Benutzeroberfläche erstellen,
쐽
wie man gleichzeitig Menüs und Ereignisse in mehreren Dokumenten verarbeitet,
쐽
wie Sie Ihre MDI-Dokumente miteinander kommunizieren lassen.
10.1 Einführung in die Mehrfachdokumentschnittstelle Was eine MDI-Anwendung bedeutet und darstellt, lernen Sie am besten am praktischen Beispiel. Abbildung 10.1 zeigt die MDI-Merkmale von Microsoft Excel in Aktion und Abbildung 10.2 zeigt Adobe Framemaker. Wäre Excel eine SDI-Anwendung, könnte man nur mit einem Dokument auf einmal arbeiten und nicht drei Dokumente gleichzeitig geöffnet halten, wie in Abbildung 10.1 zu sehen. Jede Schnittstelle – oder jedes Dokument – ist selbstständig und unabhängig von den anderen. Was man in der einen tut, betrifft die anderen nicht. Jedes Dokument kann seine jeweils eigene Menge an Daten, Objekten, Parametern usw. verwalten. Gegenüber SDI-Anwendungen bietet eine MDI-Anwendung mehrere Vorteile. Der erste ist offensichtlich: ein Zuwachs an Produktivität für den Benutzer. Theoretisch könnte er in zwei offenen Fenstern mehr Arbeit erledigen als in nur einem; das ist schon die ganze Idee, die sich hinter einem Multitasking-Betriebssystem verbirgt. Wäre es produktiver, nur eine Anwendung oder ein Dokument auf einmal zu benutzen, hätte sich das Windows-Betriebssystem wohl niemals so erfolgreich entwickelt. MDI-Anwendungen sind nützlich für Datenanalyseprogramme wie etwa Excel. Ist mehr als ein Dokument geöffnet und folglich auch mehr als eine Datenmenge zu bearbeiten, kann der Benutzer Daten leichter analysieren und vergleichen.
350
Einführung in die Mehrfachdokumentschnittstelle
Abbildung 10.1: Mit einer MDIAnwendung können Sie mehrere Schnittstellen auf einmal nutzen.
Abbildung 10.2: Adobe Framemaker stellt einen anderen Typ von MDI bereit.
Mit MDI-Anwendungen können Sie einem Benutzer ein Mehr an Informationen zur Verfügung stellen. Denken Sie z.B. an Visual Studio. Es verfügt über mehrere Fenster, um verschiedenartige Informationen anzuzeigen: Eines zeigt die Eigenschaften des aktuellen Objekts, eines die Hierarchie der Windows Forms-Steuerelemente in einem Formular und
351
MDI-Anwendungen erstellen
ein drittes den Formularcode. Werden in mehreren Fenstern unterschiedliche Facetten einer Anwendung angeboten, kann der Benutzer mehr Arbeit mit weniger Aufwand bewältigen. Ein Einzeldokument in einer MDI-Anwendung lässt sich maximal vergrößern, um alle anderen Dokumente zu verdecken. Auf diese Weise verhält sie sich fast wie eine SDI-Applikation; sogar die Menüs verschmelzen, um ein einzelnes, geöffnetes Fenster zu reflektieren. Durch diese Flexibilität kann der Benutzer die Art der Umgebung wählen, in der er gerne arbeitet. Doch MDI-Anwendungen haben auch ihre Nachteile. Als bedeutendster stellt sich oft ihr Hunger auf Systemressourcen heraus. Je mehr Dokumente man auf einmal geöffnet hat, desto mehr Rechenkapazität wird benötigt, was sich wiederum nachteilig auf die entsprechende Anwendung auswirkt, wenn nicht sogar auf Windows selbst. Dies bekommt eine erhöhte Bedeutung in Anwendungen wie Adobe Photoshop, in denen jedes Dokument große Grafiken beinhalten kann, manche bis zu mehreren Megabyte groß. Manche Anwendungen eignen sich nicht für ein Dasein als MDI. Die weiter vorn in diesem Buch entwickelte CD-Katalog-Anwendung ist ein gutes Beispiel dafür. Es gibt einfach keinen Grund, warum sie MDI-fähig sein sollte. Schließlich arbeiten Sie darin mit nur einer CD-Sammlung; MDI könnte in diesem Fall sogar zu einer gewissen Verwirrung des Benutzers beitragen. MDI ist sowohl für den Benutzer als auch den Entwickler komplizierter, was eigentlich gar nicht sein muss.
MDI-Menüs Ein interessantes Merkmal von MDI-Anwendungen ist ihr Umgang mit Menüs. Herkömmliche Menüs sind ja meist statisch: Die verfügbaren Menüs und Optionen ändern sich nicht (obwohl Microsoft Word 2000 Sie vom Gegenteil zu überzeugen versucht). Ganz gleich, was Sie tun, so haben Sie doch stets die gleichen Auswahlmöglichkeiten. Doch im Fall von MDI kann jedes Fenster seinen eigenen Satz an Menüs haben. Dieses Feature erlaubt Ihnen, bestimmte Tätigkeiten nur im aktuellen Dokument auszuführen. Je nachdem, was Sie in der Anwendung tun, können sich die Menüs ändern. Auch die Anwendung in ihrer Gesamtheit kann ihre eigenen Menüs haben, die alle Dokumente betreffen. Sie haben wahrscheinlich in den meisten MDI-Anwendungen ein FENSTER-Menü bemerkt. Es bietet Möglichkeiten, wie man die geöffneten Fenster organisieren kann, etwa durch Nebeneinanderlegen oder Übereinanderstapeln. Werden die verschiedenen Dokumente ausgewählt, so mag es vorkommen, dass Menüelemente miteinander verschmelzen, damit sie nicht mehr als einmal angezeigt werden. Dieses Verhalten lässt sich vollständig steuern.
352
Eine MDI-Anwendung erstellen
10.2 Eine MDI-Anwendung erstellen Um eine MDI-Anwendung auf die Beine zu stellen, sind mindestens zwei Dinge nötig: Erstens braucht man ein Haupt- oder übergeordnetes (Parent-) Fenster, um alle anderen offenen Dokumente zu beherbergen. Dieses übergeordnete Fenster sollte nur sehr wenige Benutzeroberflächenelemente aufweisen, denn die meisten davon sind in den anderen Dokumenten enthalten. Das übergeordnete Fenster ist eine Klasse, die von der Klasse System.Windows.Forms.Form erbt (genau wie alle anderen Windows Forms-Anwendungen). Als Zweites brauchen Sie eine Klasse, die die untergeordneten (Child-) Dokumente definiert. Sie erbt vom gleichen Namensraum wie das übergeordnete Fenster, doch sie sollte alle für die Anwendung nötigen Oberflächenelemente umfassen. Ebenso wie bei benutzerdefinierten Dialogfeldern (siehe Tag 7) brauchen Sie auch hier keine Main-Methode im untergeordneten Dokument. Wir wollen uns ein einfaches Beispiel für Parent-Child-Formulare ansehen. Als Erstes zeigt Listing 10.1 die Klasse des untergeordneten Dokuments. Listing 10.1: Die Klasse des untergeordneten MDI-Dokuments 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21:
using System; using System.Windows.Forms; using System.Drawing; namespace TYWinforms.Day10 { public class Document : Form { private Button btClick = new Button(); public Document() { btClick.Text = "Close Me!"; btClick.Location = new Point(100,100); btClick.Click += new EventHandler(this.Close); this.Controls.Add(btClick); } private void Close(Object Sender, EventArgs e) { this.Close(); } } }
Die Document-Klasse in Zeile 10 weist keine Besonderheiten auf; sie sieht genau wie jede andere Windows-Anwendung aus, nur dass sie keine Main-Methode besitzt. Beachten Sie das Steuerelement im Formular (Zeile 7), Button, das das Formular auf einen Klick hin
353
MDI-Anwendungen erstellen
schließt (Zeile 18). Beachten Sie auch den Namen der Klasse: Document. Diese Information wird im übergeordneten MDI-Fenster wichtig, das in Listing 10.2 zu sehen ist. Listing 10.2: Das übergeordnete MDI-Fenster 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18:
using System; using System.Windows.Forms; using System.Drawing; namespace TYWinforms.Day10 { public class Listing102 : Form { public Listing102() { this.IsMdiContainer = true; Document doc = new Document(); doc.MdiParent = this; } public static void Main() { Application.Run(new Listing102()); } } }
Die Klasse Listing102 in Listing 10.2 ist hinsichtlich der Anzahl der Codezeilen sogar noch einfacher. Drei Zeilen betreffen den MDI-Aspekt: 8, 10 und 11. Zeile 8 verwendet die Eigenschaft IsMdiContainer, um anzugeben, dass dieses Formular andere, nämlich untergeordnete Dokumente aufzunehmen vermag. Diese Eigenschaft ist vom Objekt System.Windows.Forms.Form geerbt, so dass Sie nun jede Windows Forms-Anwendung MDI-fähig machen können, indem Sie einfach diese Eigenschaft auf true setzen. Als Nächstes erzeugen Sie eine neue Instanz der in Listing 10.1 erstellten Document-Klasse. In Zeile 11 teilen Sie diesem neuen Objekt mit, wem es sich sozusagen unterzuordnen hat. Sie setzen die MdiParent-Eigenschaft auf das aktuelle Formular, welches durch this angegeben wird (Me in VB .NET). Auch diese Eigenschaft ist geerbt, so dass auch jede Windows Forms-Anwendung den untergeordneten Part in einer MDI-Anwendung einnehmen kann. Sobald Sie diese Anwendung kompiliert und ausgeführt haben, sehen Sie das Formular wie in Abbildung 10.3 . Sie können feststellen, dass Sie nun ein übergeordnetes Formular haben, welches einfach den Behälter für das untergeordnete Dokument darstellt. Sie können sodann das untergeordnete Dokument auf jede gewünschte Weise bearbeiten – es maximieren oder minimie-
354
Eine MDI-Anwendung erstellen
ren usw. Wenn Sie die in Listing 10.1 angelegte Schaltfläche anklicken, wird das untergeordnete Dokument geschlossen und das leere übergeordnete Formular bleibt zurück.
Abbildung 10.3: Ihre erste MDI-Anwendung sollte ungefähr so aussehen.
Sie können die Zeilen 10 und 11 wiederholt verwenden, um neue untergeordnete Dokumente zu erzeugen. Häufig bringt man diesen Code in einem Ereignishandler für Menüelemente unter, damit der Benutzer steuern kann, wann er neue Dokumente anlegt. Um dies zu erreichen, modifizieren Sie den Konstruktor von Listing 10.1 entsprechend dem folgenden Codefragment: public Document(String strName) { this.Text = strName; ...
Dieser neue Konstruktor übernimmt eine Zeichenfolge, die der Titelzeile des Formulars zugewiesen ist. In Listing 10.2 fügen Sie dann eine Variable hinzu, die die Anzahl der Dokumente und Menüobjekte verfolgt. Ändern Sie Listing 10.2 wie folgt ab: public class Listing102 : Form { private int intCounter = 0; private MainMenu mnuMain = new MainMenu(); public Listing102() { MenuItem miFile = mnuMain.MenuItems.Add("&Datei"); MenuItem miNew = miFile.MenuItems.Add("&Neu"); miNew.Click += new EventHandler(this.HandleMenu); this.Menu = mnuMain; ...
Die intCounter-Variable wird jedes Mal erhöht, wenn der Benutzer ein neues Dokument öffnet; mit Hilfe dieser Zahl lässt sich jedes neue untergeordnete Dokument leicht referen-
355
MDI-Anwendungen erstellen
zieren. Das DATEI-Menü ist ein Standardmenü mit einem NEU-Menüelement, das veranlasst, dass ein neues Dokument angelegt wird. Im nächsten Schritt verschieben Sie die Zeilen 10 und 11 aus Listing 10.2 in eine neue Methode, nämlich den Ereignishandler für das NEU-Menüelement, wie im folgenden Codeabschnitt zu sehen: public void handleMenu(Object Sender, EventArgs e) { intCounter++; Document doc = new Document("Dokument " + intCounter.ToString()); doc.mdiParent = this; doc.Show(); }
Dieser Code nutzt den modifizierten Konstruktor der zuvor erstellten Document-Klasse. Er übergibt der neuen Klasse eine Zeichenfolge, die ihre Beschriftung wird. Vergessen Sie nicht die Show-Methode aufzurufen, um das neue untergeordnete Dokument anzuzeigen. Dieser Schritt war nicht notwendig, als sich dieser Code noch im Klassenkonstruktor befand, doch das hat sich nun geändert. Nun kompilieren Sie diese Anwendung neu und probieren das NEU-Menüelement aus. Abbildung 10.4 zeigt das Ergebnis, das mehrere untergeordnete Dokumente enthält.
Abbildung 10.4: Der Benutzer kann steuern, wie viele Fenster geöffnet werden.
356
Eine MDI-Anwendung erstellen
Theoretisch könnte man unbegrenzt viele Fenster öffnen. Wollten Sie diese Anzahl begrenzen, brauchen Sie einfach nur die intCounter-Variable zu prüfen, bevor ein neues Dokument angelegt wird. Liegt die Zahlenangabe über Ihrem Schwellenwert, erzeugen Sie kein neues Document-Objekt. Beachten Sie auch, dass jedes neue Dokument eine benutzerfreundliche Titelzeile aufweist: Dokument 1, Dokument 2 usw. Wenn Sie eines der untergeordneten Fenster maximieren, verschwindet diese Titelzeile jedoch. Um diesen Makel zu beheben, legen Sie eine Überschrift für das übergeordnete MDI-Formular fest: this.Text = "Listing 10.2";
Wenn Sie nun die Fenster maximieren, ändert sich die Titelzeile von »Listing 10.2« auf eine Weise, dass sie das passende untergeordnete Fenster widerspiegelt: »Listing 10.2 – Dokument 1«, »Listing 10.2 – Dokument 2« usw. .NET handhabt einen großen Teil der Probleme, wenn man mit mehreren untergeordneten Fenstern umgeht, das ist aber noch nicht alles. Auch Menüs werden für Sie automatisch gehandhabt. Wenn ein untergeordnetes Dokument über ein verknüpftes Menü verfügt, ändert sich die Menüleiste des übergeordneten Formulars so, dass sie das aktuelle bzw. aktive Dokument widerspiegelt. Wir wollen ein einfaches Menü zur Document-Klasse hinzufügen, damit der Benutzer die Hintergrundfarbe des Formulars ändern kann; für diese Anwendung verwenden wir das gängige ColorDialogDialogfeld. Als Erstes fügen Sie das Menü ein wie im folgenden Code zu sehen: private MainMenu mnuMain = new MainMenu(); public Document(String strName){ MenuItem miFormat = mnuMain.MenuItems.Add("Format " + strName); MenuItem miColor = miFormat.MenuItems.Add("&Farbe..."); miColor.Click += new EventHandler(this.HandleMenu); this.Menu = mnuMain; ...
Beachten Sie, dass der Menüname so festgelegt wurde, dass er den Namen des untergeordneten Dokuments reflektiert. Diese Namenskonvention wird Ihnen beim Überblick über das aktive Fenster helfen. Als Nächstes fügen Sie den Ereignishandler für das Menü hinzu: private void HandleMenu(Object Sender,EventArgs e) { ColorDialog dlgColor = new ColorDialog(); if (dlgColor.ShowDialog()== DialogResult.OK) { this.BackColor = dlgColor.Color; } }
Schlagen Sie bei Bedarf noch einmal Tag 7 nach, um Ihr Gedächtnis aufzufrischen, was Standarddialogsteuerelemente betrifft. Im Wesentlichen zeigt die Methode das ColorDialog-Steuerelement an, und wenn der Benutzer eine Farbe auswählt und auf die OK-Schaltfläche klickt, ändert sich die Hintergrundfarbe des jeweiligen untergeordneten Formulars.
357
MDI-Anwendungen erstellen
Wenn Sie jetzt die Anwendung ausführen, bemerken Sie als Erstes, dass das dem untergeordneten Dokument hinzugefügte Menü in der Menüleiste des übergeordneten Formulars angezeigt wird. Dass das Menü hier platziert wird, hilft, ein Durcheinander zu vermeiden. Beachten Sie außerdem, dass sich die Menübeschriftung ändert, wenn man auf unterschiedliche untergeordnete Dokumente klickt – dies spiegelt das aktive untergeordnete Dokument wider. Sobald alle untergeordneten Fenster geschlossen wurden, verschwindet das neue FORMAT-Menü. Wir wollen noch ein weiteres verbreitetes Merkmal einer MDI-Anwendung betrachten: die Fensteranordnung. Fast jede MDI-Anwendung weist standardmäßig ein FENSTER-Menü auf, mit dem der Benutzer die untergeordneten Fenster innerhalb des übergeordneten Fensters übersichtlicher anordnen kann. Diese Fenster enthalten in der Regel eine Liste aller offenen untergeordneten Fenster, so dass man leicht jenes auswählen kann, zu dem man wechseln möchte – das erspart das Durchsuchen aller Fenster, bis man auf das gewünschte stößt. Wir wollen die verschiedenen Möglichkeiten betrachten, wie man Fenster anordnen kann: 쐽
ÜBERLAPPEND: Fenster werden bei dieser Einstellung übereinander gestapelt, mit nur gerade mal so viel Verschiebung, dass noch die Titelzeile des darunter liegenden Fensters zu sehen ist.
쐽
NEBENEINANDER: Fenster werden so angeordnet, dass sie sich nicht überschneiden. Jedes Fenster ist genau so hoch wie das übergeordnete Formular, doch die Fenster teilen sich die Breite des Hauptformulars gleichmäßig untereinander auf.
쐽
UNTEREINANDER: Der gleiche Fall, nur dass diesmal die Aufteilung senkrecht erfolgt, die Breite aber jeweils der des übergeordneten Formulars entspricht.
Diese Layouts sind in der Aufzählung MdiLayout definiert: MdiLayout.Cascade, MdiLayout.TileHorizontal und MdiLayout.TileVertical. Die LayoutMdi-Methode des FormObjekts kann diese Werte dazu verwenden, Fenster entsprechend anzuordnen. Wir wollen die Anwendung noch einmal modifizieren, um diese Layouts zu nutzen. Listing 10.3 zeigt die neue Version des Listings 10.2 und enthält alle bislang gemachten Änderungen. Listing 10.3: Das vollendete übergeordnete MDI-Formular 1: 2: 3: 4: 5: 6: 7: 8: 9: 10:
358
using System; using System.Windows.Forms; using System.Drawing; namespace TYWinforms.Day10 { public class Listing102 : Form { private int intCounter = 0; private MainMenu mnuMain = new MainMenu(); public Listing102() {
Eine MDI-Anwendung erstellen
11: 12: 13: 14:
MenuItem miFile = mnuMain.MenuItems.Add("&Datei"); MenuItem miNew = miFile.MenuItems.Add("&Neu"); MenuItem miWindow = mnuMain.MenuItems.Add("&Fenster"); miWindow.MenuItems.Add("Überlappend", new EventHandler(this.ArrangeWindows)); miWindow.MenuItems.Add("Untereinander", new EventHandler(this.ArrangeWindows)); miWindow.MenuItems.Add("Nebeneinander", new EventHandler(this.ArrangeWindows));
15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55:
miNew.Click += new EventHandler(this.HandleMenu); miWindow.MdiList = true; this.IsMdiContainer = true; this.Menu = mnuMain; this.Size = new Size(800,600); this.Text = "Listing 10.2"; } private void ArrangeWindows(Object Sender, EventArgs e) { MenuItem miTemp = (MenuItem)Sender; switch (miTemp.Text) { case "Überlappend": this.LayoutMdi(MdiLayout.Cascade); break; case "Untereinander": this.LayoutMdi(MdiLayout.TileHorizontal); break; case "Nebeneinander": this.LayoutMdi(MdiLayout.TileVertical); break; } } private void HandleMenu(Object Sender, EventArgs e) { intCounter++; Document doc = new Document("Dokument " + intCounter.ToString()); doc.MdiParent = this; doc.Show(); } public static void Main() { Application.Run(new Listing102()); } } }
359
MDI-Anwendungen erstellen
Der neue Code für das FENSTER-Menü findet sich in den Zeilen 13 bis 16. Sie erzeugen zunächst das FENSTER-Menü und dann drei Untermenüs für jede der drei Fensteranordnungen. Beachten Sie, dass sie alle auf denselben Ereignishandler namens ArrangeWindows zeigen. In Zeile 19 setzen Sie die MdiList-Eigenschaft des neuen FENSTER-Menüs auf true, um Ihrer Anwendung mitzuteilen, dass dieses Menü für die Steuerung von untergeordneten MDI-Fenstern vorgesehen ist. Im Wesentlichen wird dieses Menü Menüelemente automatisch hinzufügen und entfernen, die die gerade geöffneten untergeordneten Dokumente widerspiegeln. Werden diese dynamischen Menüelemente angeklickt, erhält das passende untergeordnete Fenster den Fokus, um Benutzereingaben zuzulassen. Springen wir zur ArrangeWindows-Methode in Zeile 27. Sie wollen feststellen, welches Menüelement für die Fensteranordnung angeklickt worden ist und die untergeordneten Fenster entsprechend arrangieren. Zuerst konvertieren Sie die Sender-Variable (welche das Objekt darstellt, das das Ereignis auslöste) in ein MenuItem, damit Sie die nötigen Eigenschaften und Methoden verwenden können. Das switch-Statement wertet die Beschriftung des angeklickten MenuItems aus. Der entsprechende case-Zweig ruft die LayoutMdi-Funktion auf und übergibt ihr einen der Werte der MdiLayout-Aufzählung.
Abbildung 10.5: Das Nebeneinanderlegen von Fenstern ist nur eine Ihrer LayoutOptionen.
360
Eine MDI-Anwendung erstellen
Nun kompilieren Sie diese Anwendung und führen sie aus. Sie werden bemerken, dass sich das FENSTER-Menü beim Hinzufügen neuer Dokumente dynamisch ändert, um die neuen Fenster zu reflektieren. Klicken Sie auf die verschiedenen Layout-Optionen, um sie zu testen. Abbildung 10.5 zeigt ein Beispiel.
Verschiedene Arten von untergeordneten Fenstern erstellen Bis jetzt haben Sie MDI-Anwendungen erstellt, die mehrere Instanzen desselben untergeordneten Dokuments aufweisen. Es ist jedoch nicht zwingend, dass Sie Ihre Anwendungen in dieser Weise erstellen: Jedes untergeordnete Dokument kann von einem völlig verschiedenen Objekttyp sein, solange nur alle von der Klasse System.Windows.Forms.Form erben. Wir wollen einmal ein brauchbares Beispiel erstellen. Viele Anwendungen (wie etwa Visual Studio) verfügen über »Symbolleisten« und »Eigenschaftenfenster«, die man auf dem Bildschirm bewegen kann. Diese Fenster werden zusätzlich zur zentralen Benutzeroberfläche bereitgestellt, welche meist für die Dateneingabe vorgesehen ist, und bieten weitere hilfreiche Funktionen an, die sonst nicht direkt zugänglich sind. Bedenken Sie jedoch, dass sich für das, was Sie mit einem untergeordneten MDI-Dokument vorhaben, möglicherweise ein benutzerdefiniertes nichtmodales Dialogfeld besser eignet. Jede Vorgehensweise hat ihre Vor- und Nachteile, daher sollten Sie Ihre Wahlmöglichkeiten genau gegeneinander abwägen, bevor Sie sich für die vielschichtige Welt von MDI entscheiden. Ich empfehle
Bitte beachten Sie
Verwenden Sie MDI und untergeordnete Dokumente, wenn Sie den Desktop des Benutzers nicht überladen möchten. und der Benutzer leicht einen Bildlauf durchführen kann, um verborgene Bereiche von untergeordneten Dokumenten einzusehen (wie zum Beispiel in Microsoft Excel).
Setzen Sie MDI nicht ein, wenn Sie ein Fenster brauchen, das mehrere untergeordnete Dokumente auf einmal beeinflussen kann. Ein Beispiel dafür wäre ein Fenster mit Schaltflächen, die automatisch Text im aktiven untergeordneten Fenster einfügen können. Würden Sie in diesem Fall ein untergeordnetes MDI-Dokument verwenden, könnten Sie den Text nicht im aktiven untergeordneten Fenster einfügen, weil dieses ja das Fenster mit den Schaltflächen wäre. Sie würden Ihre Referenz auf das beabsichtigte Ziel dieser Operation verlieren.
361
MDI-Anwendungen erstellen
10.3 Die Besonderheiten von MDI-Formularen steuern Es bereitet keine besondere Mühe, eine MDI-Anwendung zu erstellen. Um das absolute Minimum an Funktionsumfang zu erzeugen, muss man nur ein paar Codezeilen einfügen (vorausgesetzt, man hat bereits seine untergeordnete Dokumentklasse). Unter der Oberfläche erfordert das Steuern von MDI-Anwendungen jedoch wesentlich mehr.
Menüs Wenn sowohl den über- als auch untergeordneten Dokumenten Menüs zugeordnet sind, ändert sich die Menüleiste des übergeordneten Dokuments, wenn das jeweilige untergeordnete Dokument aktiv ist. Dies wird als Zusammenführen von Menüs (Menu Merging) bezeichnet; die Menüs des untergeordneten und übergeordneten Fensters verschmelzen zu einer Menüleiste. Sind die unterschiedlichen Menüs eindeutig, erfolgt die Verschmelzung anstandslos, wie Sie heute bereits bemerken konnten. Der Vorgang wird jedoch etwas interessanter, sobald man auf doppelte Menüelemente stößt. Lassen Sie uns sehen, was in einer solchen Lage passiert. Fügen Sie dem Konstruktor der Document-Klasse in Listing 10.1 folgenden Code hinzu: MenuItem miFile = mnuMain.MenuItems.Add("Datei");
Abbildung 10.6: Die Anwendung verfügt jetzt über zwei DATEI-Menüs.
362
Die Besonderheiten von MDI-Formularen steuern
Nun verfügen sowohl das übergeordnete als auch das untergeordnete Formular über ein DATEI-Menü. Kompilieren Sie diese Anwendung und führen Sie sie aus. Wenn Sie ein neues untergeordnetes Dokument öffnen, erhalten Sie die Fenster, die Sie in Abbildung 10.6 sehen. Die Menüs des übergeordneten und des untergeordneten Fensters sind jetzt miteinander verschmolzen und Sie haben zwei DATEI-Menüs! Sicherlich ist das nicht gerade das, was Sie wollen. Irgendwie müssen Sie die Anwendung wissen lassen, dass die zwei DateiMenüs eigentlich dasselbe Menü sind und dass ihre Inhalte kombiniert werden sollen. Zum Glück kann Ihnen die MenuMerge-Aufzählung helfen. Tabelle 10.1 beschreibt die Werte dieser Aufzählung. Wert
Beschreibung
Add
Das Standardverhalten. Ein untergeordnetes Menüelement wird der übergeordneten Menüleiste hinzugefügt, ganz gleich, ob bereits eines existiert (dies führt zum Problem des »doppelten DATEI-Menüs«).
MergeItems
Alle Untermenüs dieses Menüelements werden mit denen des übergeordneten Menüs, die den gleichen Platz im Menü innehaben, zusammengeführt.
Remove
Dieses MenuItem wird beim Zusammenführen nicht verwendet; es wird vielmehr weggelassen und nicht in der kombinierten Menüleiste auftauchen.
Replace
Dieses MenuItem wird jedes vorhandene MenuItem, das den gleichen Platz in dem Menü innehat, ersetzen. Das untergeordnete Menüelement hat stets Vorrang vor dem des übergeordneten. Sie verwenden diesen Wert, wenn Sie die Ereignishandler für ein bestimmtes Menüelement überschreiben wollen.
Tabelle 10.1: Die Werte der MenuMerge-Aufzählung
Damit alle Werte (außer Remove) funktionieren, muss allen MenuItems ein MenuMerge-Wert zugewiesen werden – d.h. sowohl im übergeordneten als auch im untergeordneten Formular. Um den Wert zuzuweisen, fügen Sie in die Listings 10.1 und 10.2 folgende Codezeile ein: miFile .MergeType = MenuMerge.MergeItems;
Wenn Sie nun die Anwendung ausführen und ein untergeordnetes Dokument anlegen, erscheint nur ein DATEI-Menü. Versuchen Sie nun dem Listing 10.1 folgende Zeile hinzuzufügen; sie fügt einfach ein Untermenü in das DATEI-Menü des untergeordneten Dokuments ein: MenuItem miNew = miFile.MenuItems.Add("Neu");
363
MDI-Anwendungen erstellen
Führen Sie die Anwendung erneut aus. Was passiert, wenn Sie ein untergeordnetes Dokument anlegen? Die DATEI-Menüs verschmelzen, aber nun haben Sie zwei NEU-Untermenüs. Um dieses Problem zu beheben, können Sie entweder in beiden Listings MergeType = MenuMerge.Replace festlegen (was dazu führen würde, dass das Menüelement des untergeordneten Dokuments das des übergeordneten Formulars ersetzen würde) oder Sie geben in beiden Listings MergeType = MenuMerge.Remove an. Das übrig bleibende Menüelement entspricht der entgegengesetzten Klasse, in der Sie MenuMerge.Remove angeben. Wenn Sie z.B. Remove in der übergeordneten Klasse angeben, dann ist das untergeordnete Menüelement dasjenige, das in der Menüleiste angezeigt wird; nur seine Funktion wird ausgeführt, sobald man es anklickt. Beachten Sie, dass das ganze Thema der Zusammenführung nur Elemente betrifft, die im Menü den gleichen Platz innehaben. Angenommen, Sie haben ein Menü des übergeordneten Dokuments mit folgenden Elementen (von oben nach unten): DATEI, NEU, SPEICHERN, SCHLIEßEN. Ein korrespondierendes untergeordnetes Dokumentenmenü besitzt folgende Elemente (wiederum von oben nach unten): DATEI, NEU, SCHLIEßEN. Die jeweiligen Menüs DATEI und NEU sollten gemäß den soeben genannten MenuMerge-Regeln gehandhabt werden. Mit den Menüelementen danach gibt es jedoch ein kleines Problem. Stellen Sie sich vor, Sie wollen das SCHLIEßEN-Menü des untergeordneten Dokuments das SCHLIEßEN-Menü des übergeordneten Dokuments überschreiben lassen. Daher setzen Sie MergeType = MenuMerge.Remove in der übergeordneten Klasse und MergeType = MenuMerge.Replace in der untergeordneten Klasse. Dieses Vorgehen sollte funktionieren, oder? Leider klappt das nicht ganz. Das SCHLIEßEN-Menü des übergeordneten Dokuments verschwindet zwar wie erwartet, doch das übergeordnete SPEICHERN-Menü wird nun durch das untergeordnete SCHLIEßEN-Menü ersetzt, so dass das Menüsystem durcheinander gerät und der Benutzer ernsthaft verwirrt ist. Obwohl wir wissen, dass das SCHLIEßEN-Menü des übergeordneten Dokuments dem gleichnamigen Menü des untergeordneten Dokuments entsprechen sollte, weiß es die Anwendung nicht. Vielmehr kann sie nur auf der Grundlage der Position des Menüelements im Menü operieren. SCHLIEßEN befindet sich im übergeordneten Menü an Position 3 (die Zählung beginnt bei Null) und im untergeordneten Menü an Position 2. Die Wege dieser beiden Menüelemente werden sich also nie kreuzen, weshalb sie auch nie verschmelzen können. Die Position 2 im übergeordneten Menü wird nämlich vom SPEICHERN-Menü eingenommen, so dass es vom MergeType des untergeordneten SCHLIEßENMenüs betroffen wird. Doch zum Glück gibt es auch für dieses Problem eine Lösung. Die MergeOrder-Eigenschaft von MenuItems lässt sich dazu benutzen, eine Reihenfolge zu diktieren und somit auch die Parent-Child-Menübeziehung, in der die Menüelemente auftauchen. Im obigen Beispiel können Sie beide SCHLIEßEN-Menüelemente auf denselben MergeOrder-Wert setzen:
364
Die Besonderheiten von MDI-Formularen steuern
miClose.MergeOrder = 3
Nun weiß die Anwendung nicht nur, dass diese beiden Menüelemente einander entsprechen, sondern sie wird sie auch in der aufsteigenden Reihenfolge der MergeOrder-Werte anordnen. Mit anderen Worten: Ein Menüelement mit MergeOrder gleich 0 wird an der Spitze des Menüs platziert, das Element mit MergeOrder gleich 1 folgt unmittelbar darunter usw. Beachten Sie, dass die Menüelemente nicht an die durch MergeOrder vorgeschriebene Position verschoben werden. Daher ist die Anfangsposition weiterhin durch die Reihenfolge diktiert, in der Sie MenuItems zum Menü hinzufügen. Welchen MergeOrder-Wert Sie nun tatsächlich setzen, ist eigentlich nicht wichtig; Sie können ohne weiteres die MergeOrder des SCHLIEßEN-Menüelements auf 1000 setzen. Windows Forms ist intelligent genug, um keine 999 leeren Menüelemente vor das fragliche Menüelement zu setzen (es sei denn, Sie befehlen, 999 leere Stellen einzufügen). Die MergeOrder ist nur eine willkürliche Zahl, die die Parent-Child-Beziehungen und die Anordnung von zusammengeführten Elementen regelt. Elemente mit höherer MergeOrderZahl werden in einem Menü stets tiefer einsortiert als Elemente mit einer niedrigeren MergeOrder. Daher mag es nicht in jedem Fall von Vorteil sein, die MergeOrder-Werte fortlaufend zu nummerieren, also 0 für das erste Element, 1 für das zweite und so weiter. Was, wenn Ihre untergeordnete Anwendung ein Menüelement hat, das Sie zwischen zwei Menüelementen im übergeordneten Menü einschieben wollen? In diesem Fall würde es helfen, zwischen den MergeOrder-Werten einen gewissen Abstand vorzusehen, so dass man später noch expandieren kann. Abbildung 10.7 illustriert das geschilderte Konzept.
Untergeordnete Fenster In vielen Fällen müssen Sie wissen, welches untergeordnete MDI-Fenster das aktive ist. Angenommen, Sie führen eine Suche in einem RichTextBox-Steuerelement durch oder fügen Text aus einer anderen Anwendung ein. Die Eigenschaft ActiveMdiChild des übergeordneten Formulars liefert ein Form-Objekt, das dem untergeordneten Fenster entspricht. Wir wollen Ihrem übergeordneten Formular ein FARBE-Menü hinzufügen, das ein ColorDialog-Dialogfeld anzeigt und die Farbe des aktiven untergeordneten Fensters ändert. Zu diesem Zweck fügen Sie dem Listing 10.2 folgenden Code hinzu: MenuItem miFormat = mnuMain.MenuItems.Add("Farbe "); MenuItem miColor = miFormat.MenuItems.Add("&Farbe..."); miColor.Click += new EventHandler(this.ChangeColor);
365
MDI-Anwendungen erstellen
Elternmenü
Kindmenü
Neu, MergeOrder = 0 Öffnen, MergeOrder = 5 Drucken, MergeOrder = 10 Beenden, MergeOrder = 100
Neu, MergeOrder = 0 Speichern, MergeOrder = 2 Speichern als, MergeOrder = 3 Rückgängig, MergeOrder = 9 Schließen, MergeOrder = 11
Merged Menu Neu, MergeOrder = 0 Speichern, MergeOrder = 2 Speichern als, MergeOrder = 3 Öffnen, MergeOrder = 5 Rückgängig, MergeOrder = 9 Drucken, MergeOrder = 10 Schließen, MergeOrder = 11 Beenden, MergeOrder = 100
Abbildung 10.7: Die genauen MergeOrder-Werte sind unwesentlich; nur auf die relativen Werte kommt es an.
Nun fügen Sie den ChangeColor-Ereignishandler hinzu: private void ChangeColor(Object Sender,EventArgs e){ ColorDialog dlgColor = new ColorDialog(); if (dlgColor.ShowDialog()== DialogResult.OK){ if (ActiveMdiChild != null){ ActiveMdiChild.BackColor = dlgColor.Color; } } }
Diese Methode erstellt zunächst ein Dialogfeld ColorDialog und zeigt es an, damit der Benutzer eine Farbe auswählen kann. Bevor Sie fortfahren, müssen Sie prüfen, ob der Benutzer in der Anwendung schon ein untergeordnetes Fenster erzeugt hat. Ist das nicht der Fall und versuchen Sie auf die Eigenschaft ActiveMdiChild zuzugreifen, erhalten Sie eine Fehlermeldung. Zum Schluss legen Sie die Eigenschaften des ActiveMdiChild-Formulars genau wie für jedes andere Form-Objekt fest. Sie können sich auch benachrichtigen lassen, sobald ein untergeordnetes Fenster aktiviert wird oder vom Benutzer den Fokus erhält. Dafür steht das Ereignis MdiChildActivate zur Verfügung. Fügen Sie den folgenden Code Ihrem übergeordneten Formular hinzu: //außerhalb des Konstruktors private StatusBar sbarMain = new StatusBar(); //innerhalb des Konstruktors this.Controls.Add(sbarMain); this.MdiChildActivate += new EventHandler(this.UpdateStatus);
366
Die Besonderheiten von MDI-Formularen steuern
Als Nächstes fügen Sie die UpdateStatus-Methode ein: private void UpdateStatus(Object Sender,EventArgs e) { sbarMain.Text = ActiveMdiChild.Text +": " + ActiveMdiChild.BackColor.ToString(); }
So einfach ist das! Abbildung 10.8 zeigt ein Beispiel.
Abbildung 10.8: Abbildung 10.8: Das Ereignis MdiChildActivate
kann Sie darüber unterrichten, sobald ein untergeordnetes Fenster den Fokus erhält.
Sollten Sie jemals auf ein untergeordnetes Dokument zugreifen müssen, bei dem es sich nicht um das aktive untergeordnete Dokument handelt, können Sie die MdiChildrenEigenschaft einsetzen, die ein Array aller untergeordneten Formulare liefert, seien sie nun aktiv oder nicht.
Auf untergeordnete Steuerelemente zugreifen Der Zugriff auf untergeordnete Formulare ist also nicht besonders schwierig. Das ist jedoch beim Zugriff auf ihre Steuerelemente ganz anders. Da Sie schließlich praktisch alle Steuerelemente mit Hilfe des private-Modifizierers erstellt haben, dürften sie über das übergeordnete Formular nicht zugänglich sein, oder? Zum Glück lautet die Antwort »nein«. Die in einem untergeordneten Formular enthaltenen Steuerelemente können Sie über die Controls-Eigenschaft des untergeordneten Dokuments erreichen. Betrachten Sie bitte den in einem übergeordneten Konstruktor enthaltenen Code:
367
MDI-Anwendungen erstellen
string StrText; Document doc = new Document("Neues Dokument"); doc.MdiParent = this; strText = doc.Controls[0].Text;
Dieser Befehl ruft die Text-Eigenschaft dessen ab, was auch immer das erste Steuerelement im untergeordneten Formular ist. Wir wollen ein realistischeres Beispiel erstellen. Listing 10.4 zeigt ein neues übergeordnetes Anwendungsformular, das ein neues untergeordnetes Formular erzeugt, zu dem wir gleich noch kommen. (Der Einfachheit halber wurden die standardmäßigen Windows-Menüs und Ereignishandler in diesem Code weggelassen.) Listing 10.4: Ein eindringendes übergeordnetes Steuerelement 1: using System; 2: using System.Windows.Forms; 3: using System.Drawing; 4: 5: namespace TYWinforms.Day10 { 6: public class Listing104 : Form { 7: private int intCounter = 0; 8: private MainMenu mnuMain = new MainMenu(); 9: 10: public Listing104() { 11: MenuItem miFile = mnuMain.MenuItems.Add("&Datei"); 12: MenuItem miNew = miFile.MenuItems.Add("&Neu"); 13: MenuItem miOpenIn = miFile.MenuItems.Add("Ö&ffnen in neuem Fenster"); 14: miOpenIn.Enabled = false; 15: 16: miFile.Popup += new EventHandler (this.EnableOpenIn); 17: miNew.Click += new EventHandler(this.HandleMenu); 18: miOpenIn.Click += new EventHandler(this.OpenIn); 19: miWindow.MdiList = true; 20: 21: this.IsMdiContainer = true; 22: this.Menu = mnuMain; 23: this.Size = new Size(800,600); 24: this.Text = "Listing 10.3"; 25: } 26: 27: private void EnableOpenIn(Object Sender, EventArgs e) { 28: if (this.ActiveMdiChild != null) { 29: if (((TextBox)ActiveMdiChild.Controls[0]).SelectedText != "") { 30: mnuMain.MenuItems[0].MenuItems[1].Enabled = true;
368
Die Besonderheiten von MDI-Formularen steuern
31: } else { 32: mnuMain.MenuItems[0].MenuItems[1].Enabled = false; 33: } 34: } 35: } 36: 37: private void OpenIn(Object Sender, EventArgs e) { 38: if (ActiveMdiChild != null) { 39: string strText = ((TextBox)ActiveMdiChild.Controls[0]).SelectedText; 40: 41: intCounter++; 42: Document2 doc = new Document2("Document " + intCounter.ToString()); 43: doc.MdiParent = this; 44: doc.Controls[0].Text = strText; 45: doc.Show(); 46: } 47: } 48: 49: private void HandleMenu(Object Sender, EventArgs e) { 50: intCounter++; 51: 52: Document2 doc = new Document2("Dokument " + intCounter.ToString()); 53: doc.MdiParent = this; 54: doc.Show(); 55: } 56: 57: public static void Main() { 58: Application.Run(new Listing104()); 59: } 60: } 61: }
Listing 10.4 beginnt mit einem normalen übergeordneten MDI-Formular. Sie haben ein DATEI-Menü, mit dem der Benutzer neue untergeordnete Dokumente anlegen kann. Sein Ereignishandler ist in Zeile 49 zu finden. Diese HandleMenu-Methode ist praktisch die gleiche wie in Listing 10.2, doch erzeugt sie diesmal die Instanz eines untergeordneten Dokuments Document2 statt nur Document. Sie werden diese neue Klasse gleich sehen; sie ist im Grunde sehr einfach. Wir haben außerdem in Zeile 13 ein neues Menüelement namens IN NEUEM FENSTER ÖFFNEN. Dieses Menüelement übernimmt jeden Text, den der Benutzer in einem untergeordneten Fenster ausgewählt hat, erzeugt ein neues untergeordnetes Dokument und legt nur den ausgewählten Text im neuen
369
MDI-Anwendungen erstellen
Dokument ab. (Sie merken schon, dass die neue untergeordnete Klasse über wenigstens eine TextBox-Steuerelement verfügt.) Ist jedoch kein untergeordnetes Dokument vorhanden, soll der Benutzer dieses Menüelement nicht benutzen können. Um diesen Aspekt zu steuern, weisen Sie dem Popup-Ereignis des DATEI-Menüs einen Delegaten zu (Zeile 16). Das Ereignis wird immer dann ausgelöst, wenn der Benutzer das Menü öffnet. Zeile 27 enthält den Ereignishandler für das Popup-Ereignis. Sie wollen das Menüelement ÖFFNEN IN nur dann »einschalten«, sofern a) ein untergeordnetes Dokument aktiv ist und b) dieses untergeordnete Dokument markierten Text enthält. Das erste if-Statement in Zeile 28 prüft, ob ein untergeordnetes Dokument aktiv ist. Das zweite ist ein wenig komplizierter. Denken Sie daran, dass Sie auf die Steuerelemente in einem untergeordneten Formular mit Hilfe der Controls-Eigenschaft zugreifen können. Zwar trifft dies zu, doch diese Eigenschaft liefert nur ein Control-Objekt, so dass Sie es in ein passendes Steuerelement konvertieren müssen. In diesem Fall erwarten Sie ja ein TextBox-Steuerelement. Das if-Statement in Zeile 29 holt das erste Steuerelement im aktiven untergeordneten Dokument, konvertiert es in den Typ TextBox und wertet dann die SelectedText-Eigenschaft daraufhin aus, ob ein Text markiert worden ist. Ist dies nicht der Fall, soll das Menüelement abgeschaltet werden, wie in Zeile 32 angegeben. Ist jedoch wirklich etwas Text markiert, dann soll das Menüelement ÖFFNEN IN aktiviert werden. Dies ist der erste Teil des Zugriffs auf die Steuerelemente im untergeordneten Dokument. Der nächste Schritt besteht im Bereitstellen der Funktionalität von ÖFFNEN IN. Diese wird von der OpenIn-Methode in Zeile 37 gehandhabt. Wiederum prüfen Sie in Zeile 38 zuerst, ob ein untergeordnetes Dokument aktiv ist. In der nächsten Zeile rufen Sie den markierten Text (SelectedText) aus dem ersten Steuerelement des untergeordneten Dokuments, wobei Sie wieder daran zu denken haben, das Objekt per Typumwandlung passend zu konvertieren. Dann erzeugen Sie ein neues untergeordnetes Dokument auf die gleiche Weise, wie wenn ein Benutzer das NEU-Menüelement anklicken würde: Sie inkrementieren den Zähler, erstellen ein neues Document2-Objekt, legen seine MdiParent-Eigenschaft auf das aktuelle Formular fest und rufen schließlich zwecks Anzeige die Show-Methode auf. In Zeile 44 ist ein gesonderter Arbeitsschritt zu sehen: Sie setzen die Text-Eigenschaft des ersten Steuerelements im neuen untergeordneten Dokument auf den Text, der aus dem letzten untergeordneten Dokument geholt wurde. Wir wollen zum Schluss noch einen Blick auf die neue Document2-Klasse in Listing 10.5 werfen.
370
Die Besonderheiten von MDI-Formularen steuern
Listing 10.5: Die Klasse Document2 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18:
using System; using System.Windows.Forms; using System.Drawing; namespace TYWinforms.Day10 { public class Document2 : Form { private TextBox tbText = new TextBox(); public Document2(String strName) { tbText.Dock = DockStyle.Fill; tbText.Multiline = true; tbText.HideSelection = false; this.Text = strName; this.Controls.Add(tbText); } } }
Listing 10.5 zeigt eine einfache Klasse, die lediglich ein TextBox-Steuerelement enthält. Die TextBox wird in Zeile 10 so eingestellt, dass sie das gesamte Formular ausfüllt, so dass der Benutzer umfangreiche Textmengen eingeben kann.
Abbildung 10.9: Nachdem Sie etwas Text markiert und auf das Menüelement ÖFFNEN IN geklickt haben, erscheint Ihr ausgewählter Text in dem neuen Dokumentfenster.
371
MDI-Anwendungen erstellen
Kompilieren Sie diese Anwendung und führen Sie sie aus. Prüfen Sie die Funktionsfähigkeit, indem Sie etwas Text markieren und auf das Menüelement ÖFFNEN IN klicken. Abbildung 10.9 zeigt, wie Ihre Anwendung aussehen sollte.
10.4 Zusammenfassung MDI-Anwendungen nutzen sinnvoll das Multitasking-Konzept. Wenn mehr als ein Dokument auf einmal in einer Anwendung geöffnet ist, könnte ein Benutzer theoretisch doppelt so viel Arbeit verrichten. Das Erstellen einer MDI-Anwendung erweist sich häufig als wirtschaftlicher, als den Benutzer zu zwingen, eine fragliche Anwendung zweimal zu starten, nur um ihn in zwei Dokumenten arbeiten lassen zu können. Um eine MDI-Anwendung erstellen zu können, braucht man eine Klasse für das übergeordnete Formular und eine für das untergeordnete Formular. Dabei kann es sich um beliebige Klassen handeln, solange sie vom Form-Objekt erben. Beliebige Windows Forms können entweder übergeordnete oder untergeordnete MDI-Formulare sein; hier gibt es keine Einschränkung. Die meisten MDI-Anwendungen verfügen über ein FENSTER-Menü, mit dem der Benutzer die untergeordneten Fenster in einer übersichtlicheren Weise anordnen kann. Ein Menü können Sie leicht wie folgt erstellen: Erzeugen Sie einfach die nötigen Menüelemente und rufen Sie die LayoutMdi-Methode mit dem passenden Wert der MdiLayout-Aufzählung auf. Sie können zudem die MdiList-Eigenschaft dieses Menüs auf true setzen, um automatisch die untergeordneten Dokumente als Menüelemente auflisten zu lassen, so dass der Benutzer damit leichter ein Dokument zur Arbeit auswählen kann. Menüs bilden in MDI-Anwendungen ein recht interessantes Thema. Sowohl übergeordnete als auch untergeordnete Dokumente können über verknüpfte Menüs verfügen, doch die Probleme beginnen, wenn die beiden miteinander verschmelzen müssen. Doch die Eigenschaften MergeType und MergeOrder lösen diese Probleme, weil Sie damit die Anwendung instruieren können, welche Menüs auf welche Weise angezeigt werden sollen. Um das aktuelle bzw. aktive untergeordnete Fenster in einer MDI-Anwendung zu entdecken, rufen Sie die ActiveMdiChild-Eigenschaft des übergeordneten Formulars auf. Das MdiChildActivate-Ereignis wird ausgelöst, wenn ein untergeordnetes Fenster aktiviert wird. Sie können auf die in den untergeordneten Dokumenten enthaltenen Steuerelemente zugreifen, indem Sie die Controls-Eigenschaft des untergeordneten Objekts verwenden; denken Sie jedenfalls daran, den zurückgelieferten Wert bei Bedarf zu konvertieren.
372
Fragen und Antworten
10.5 Fragen und Antworten F
Wenn ich eine bestimmte Anzahl von Fenstern geöffnet habe (etwa vier oder fünf), dann ordnen die Befehle zum Neben- und Untereinanderlegen die Fenster auf gleiche Weise an. Was ist der Grund dafür? A
Die Anwendung versucht, die Fenster so anzuordnen, dass der vorhandene Platz am wirtschaftlichsten ausgenutzt wird. Sie versucht jedes untergeordnete Fenster größtmöglich anzuzeigen. Je nach der Bildschirmgröße erzeugt das Nebeneinanderlegen eine zweite Fensterspalte, wenn mehrere Fenster geöffnet sind; doch ab einem bestimmten Punkt des Aufteilens der jeweiligen Höhe kann man außer der Titelzeile jedes untergeordneten Fensters nichts mehr sehen. Der Punkt, an dem eine zweite Spalte (bzw. eine Zeile beim Untereinanderlegen) erzeugt wird, kann sowohl für Neben- als auch Untereinanderlegen der gleiche sein. In bestimmten Situationen bleibt nur noch eine Möglichkeit übrig, um Fenster anzuordnen, und sowohl Neben- als auch Untereinanderlegen nutzen diese eine Möglichkeit.
F
Wie veranlasse ich meine untergeordneten Fenster dazu, in einer Ecke oder an einer Seite des übergeordneten Dokuments anzudocken? A
Leider ist das Andocken von untergeordneten Fenstern nicht so einfach, wie es aussieht. Die Dock-Eigenschaft wirkt sich auf Form-Objekte nicht so aus wie auf andere Steuerelemente, so dass die Verwendung eines DockStyle-Wertes nichts bewirkt und die in vielen Anwendungen übliche »Einrastfunktion« nicht verfügbar ist. Eine Methode besteht darin, einen Ereignishandler für das LocationChangedEreignis des untergeordneten Fensters zu erstellen. Wird dieses Ereignis ausgelöst, können Sie die Position des fraglichen untergeordneten Fensters auswerten, und falls es sich in einem bestimmten Bereich (sagen wir, 10 Pixel vom linken Rand des übergeordneten Fensters entfernt) befindet, können Sie die Position und Größe anpassen, um den Eindruck des Andockens und »Einrastens« zu vermitteln. Ein typischer Ereignishandler würde wie folgt aussehen: private void SnapIt(Object Sender, EventArgs e) { Form frmTemp = (Form)Sender; if (frmTemp.Location.X < 10 & frmTemp.Location.Y < 10) { frmTemp.Location = new Point(0,0); frmTemp.Size = new Size(100,550); } }
Beachten Sie, dass diese Methode das untergeordnete Fenster nicht direkt andockt, aber zumindest eine gute Alternative dazu bietet.
373
MDI-Anwendungen erstellen
F
Ich habe ein untergeordnetes Fenster vom Symbolleistentyp erstellt, doch ich kann es nicht über die Grenzen des übergeordneten Fensters hinaus bewegen. In Photoshop und Visual Studio geht das jedoch. Was ist los? A
In diesen anderen Anwendungen haben Sie es nicht wirklich mit untergeordneten MDI-Fenstern zu tun. Vielmehr handelt es sich bei diesen Symbolleisten um angepasste Dialogfeldklassen, die sich an jede beliebige Position bewegen lassen und daher nicht einmal an die Grenzen des übergeordneten Fensters gebunden sind. Sollten Sie also eine Methode der anderen vorziehen? Die Antwort hängt von der zu erstellenden Anwendung ab. Wenn Sie zudem die Arbeitsfläche des Benutzers nicht überladen wollen, wäre es eine gute Idee, statt benutzerdefinierten Dialogfeldern untergeordnete MDI-Fenster anzulegen.
10.6 Workshop Dieser Workshop soll Ihnen helfen, sich besser an die heute behandelten Begriffe zu erinnern. Es hilft Ihnen sehr, die Antworten vollständig zu verstehen, bevor Sie weiterlesen. Die Antworten zu den Quizfragen und den Übungen finden Sie in Anhang A.
Quiz 1. Welche Eigenschaft müssen Sie im übergeordneten MDI-Formular einstellen, um es zu einer MDI-Anwendung zu machen? 2. Welche Eigenschaft müssen Sie im untergeordneten MDI-Dokument einstellen, um es zum Bestandteil einer MDI-Anwendung zu machen? 3. Über welche drei Werte verfügt die Aufzählung MdiLayout? 4. Wahr oder falsch? Sie müssen erst die Show-Methode aufrufen, bevor Sie ein untergeordnetes MDI-Formular anzeigen können, wenn es im Konstruktor seines übergeordneten Formulars erzeugt wird. 5. Der folgende Code veranlasst das TextBox-Steuerelement tbText nicht dazu, das gesamte Formular auszufüllen: TextBox tbText = new TextBox(); tbText.Dock = DockStyle.Fill;
Warum nicht?
374
Workshop
6. Wie findet man heraus, ob ein untergeordnetes MDI-Fenster aktiv ist? 7. Sie haben drei Menüelemente in einem untergeordneten Dokument erzeugt, aber sie werden im übergeordneten Dokument nicht richtig angezeigt. Nennen Sie drei Dinge, die man zur Behebung dieses Problems prüfen sollte. 8. Wahr oder falsch? Der MergeOrder-Wert muss sich schrittweise erhöhen lassen; Sie können keine Werte in der Reihenfolge überspringen.
Übung Erstellen Sie eine MDI-Version der gestrigen Anwendung für die Ausführung von SQLStatements. Erzeugen Sie ein benutzerdefiniertes Dialogfeld, das ein Textfeld anzeigt, in dem der Benutzer seine Abfrage eingeben kann.
375
Arbeiten mit der Windows-Ein-/ Ausgabe
1 1
Arbeiten mit der Windows-Ein-/Ausgabe
Das Arbeiten mit Ein- und Ausgabe (E/A) ist ein wichtiger Bestandteil des Umgangs mit jedem Betriebssystem. Dazu gehört mehr als nur das Lesen und Schreiben von Dateien. E/A umfasst auch Drucken, Netzwerkzugang, Überwachung von Änderungen im Dateisystem und so weiter. Die heutige Lektion konzentriert sich auf verschiedene Aspekte der E/A in .NET Windows Forms. Sie werden erkennen, dass .NET den einst komplizierten Vorgang einfach gemacht hat, so dass Sie in kürzester Zeit Dokumente speichern werden! Heute lernen Sie, 쐽
was Streams sind und wie man sie verwendet,
쐽
wie man Streams liest und in sie schreibt,
쐽
wie man eine Oberfläche im Explorer-Stil erstellt,
쐽
wie man Textdateien bearbeitet,
쐽
die am Drucken eines Dokuments beteiligten Arbeitsschritte.
11.1 Einführung in Streams Was ist eine Datei? Vom Standpunkt des Benutzer aus stellt eine Datei eine Information auf der Festplatte eines Computers dar. Sie hat einen Namen, einen Verzeichnispfad und eine Namenserweiterung; ihre Eigenschaften wie etwa Größe, Erstellungsdatum usw. lassen sich leicht ablesen. Wenn ein Benutzer eine Datei erstellt, geht er davon aus, dass sie nicht verschwinden, sondern auf die Festplatte geschrieben wird, es sei denn, eine Katastrophe träte ein. Für den Entwickler hingegen sieht eine Datei ein wenig anders aus. Man muss in Begriffen von Bits und Bytes denken, so dass eine Datei im Grunde eine Sammlung von Einsen und Nullen ist, die auf der Festplatte gespeichert wird. Damit eine bestimmte Ansammlung von Bits einen Sinn ergibt, müssen sie auf eine bestimmte Art und Weise angeordnet sein, und unterschiedliche Dateitypen weisen jeweils ihre gesonderten Schemas auf. Eine Datei ist stets mit einer Position oder Verzeichnisstruktur auf dem Computer verknüpft. Dieser Mechanismus eignet sich hervorragend für den normalen Gebrauch und ist perfekt, wenn es um vordefinierte Speichermechanismen geht. Wenn man weiß, welche Dateien man verwenden wird, erweist sich dieses Verfahren der sequentiellen Speicherung von Bits als ideal. Es hat jedoch auch seine Unzulänglichkeiten. Die herkömmliche Ein-/Ausgabe befasst sich nur mit der E/A von Dateien und kann keinen anderen Speichermechanismus handhaben. Man muss die richtige Bitsequenz und häufig sogar den Verzeichnispfad liefern, damit sie richtig funktioniert. Auf Grund der Natur eines Betriebssystems wie Win-
378
Einführung in Streams
dows und der Neigung der Benutzer, stets das Unerwartete zu tun, ist dieser Systemtyp anfällig für Fehler. Das .NET Framework führt erstmals den Begriff Stream (Strom) ein. Zwischen einem Stream und einer Datei gibt es einen Hauptunterschied: Ein Stream kann von überallher stammen und er muss nicht auf der Festplatte gespeichert werden wie eine Datei. Er kann aus dem Speicher kommen, aus einem Netzwerk, von der Sicherheitskopie auf einem Bandlaufwerk usw. Die Position ist willkürlich, genau wie die Datenquelle für ein DataSet. Diese Position wird als der Sicherungsspeicher eines Streams bezeichnet. Der wichtigste Aspekt an einem Stream, den man sich merken sollte, ist der, dass es sich lediglich um einige Daten handelt, die von einer Quelle unabhängig sind. Auf diese Weise muss sich ein Entwickler nicht mit den Schwierigkeiten beim Umgang mit einer bestimmten Datenquelle herumschlagen. Die Art und Weise, wie ein Stream genau funktioniert, mag Sie ein wenig verwirren. Er lädt nicht die gesamte Datenquelle in den Speicher und er lässt die Datenquelle auch nicht im bekannten Internetsinne »strömen« (wie etwa ein Streaming-Video). Stellen Sie sich einen Stream vielmehr als Ihren persönlichen Butler vor. Er ist der Vermittler zwischen Ihnen und den verknüpften Daten und umgekehrt. Somit kann Ihr Butler Daten zur Datenquelle bringen und von dort welche holen. Er kann Ihnen Elemente der Quelle entweder in der Reihenfolge bringen, wie er sie antrifft oder in jeder anderen Weise, die Sie wählen; Sie könnten ihm etwa befehlen, ein Mittelstück aus einer Quelle zu besorgen. Abbildung 11.1 illustriert dieses Verteilungssystem.
nDate e quell
Strom
nDate e l e u q l
Abbildung 11.1: Ein Stream fungiert als eine Art Butler, um Informationen von Ihnen an eine Datenquelle auszuliefern oder umgekehrt.
Man kann vier verschiedene Arten von Streams verwenden, je nach deren Quelltyp: FileStream, MemoryStream, NetworkStream und CryptoStream (Letzterer ist an kryptographische Daten, die der Sicherheit dienen, geknüpft). Darüber hinaus stellt das Objekt BufferedStream lediglich eine Verbindung zu einem anderen Stream her. Es stellt diesen Streams Pufferfunktionen zur Verfügung. Zudem lassen sich die Streams miteinander verknüpfen, falls einer für eine Aufgabe nicht ausreichen sollte. So können Sie z.B. ein FileStream-Objekt einsetzen, um Daten aus einer Datei zu lesen und dann ein NetworkStream-Objekt, um diese Daten über ein Netzwerk zu übertragen, ohne dabei mittendrin abbrechen zu müssen. Auch hier ist die ursprüngliche Datenquelle gleichgültig; ein Stream lässt sich mit fast allem verbinden.
379
Arbeiten mit der Windows-Ein-/Ausgabe
Diesem Puzzle der Streams fehlt noch ein Stückchen: Streamreader und -writer. Sie helfen Ihnen bei der Interaktion mit den Streaminhalten. Wie ihre Namen andeuten, lesen sie aus oder schreiben in einen Stream. Um auf die Butler-Analogie zurückzukommen: Es kann vorkommen, dass Ihr Butler nicht die gleiche Sprache wie Sie spricht. Daher benötigen Sie einen Übersetzer, der Ihre Befehle an den Butler weitergibt und Sie informiert, was der fremdsprachige Butler zu sagen hat. Stellen wir uns eine konkrete .NET-Situation vor. Angenommen, Sie haben eine XMLDatei. Um die Datei zu öffnen und auf deren Inhalt zuzugreifen, können Sie einen FileStream einsetzen. Als Nächstes benötigen Sie jedoch ein XmlReader-Objekt, das die Informationen aus dem Stream holen kann, wobei es Nullen und Einsen bei diesem Vorgang in XML-Inhalte übersetzt. Alternativ können Sie einen StringReader einsetzen, der den Stream lesen und eine umfangreiche Zeichenfolge liefern könnte. Offensichtlich ist der XmlReader in diesem Fall besser für die Aufgabe geeignet, denn damit können Sie spezifische XML-Funktionen anwenden, die der StringReader nicht anbietet. Abbildung 11.2 illustriert die Kette der Objekte, mit der Sie interagieren müssen, um mit Ein-/Ausgabe unter Windows umzugehen. Leser oder Schreiber
beliebiger Datenspeicher
Strom
Abbildung 11.2: Ihr Stream kann Daten aus einer Datenquelle liefern und ein Reader oder Writer kann Ihnen die Daten in verwendbarem Format liefern.
11.2 Dateien und Verzeichnisse Bevor Sie anfangen, Dateien zu lesen und zu schreiben, müssen Sie wissen, wie Sie mit diesen generischen Objekten umgehen. Mit Hilfe von vier .NET-Objekten können Sie Dateien und Ordner in einem Dateisystem manipulieren: File, FileInfo, Directory und DirectoryInfo. Die Objekte File und FileInfo ähneln einander; sie enthalten zu einem großen Teil die gleichen Mitglieder. Der Unterschied besteht darin, dass das File-Objekt rein statisch ist. Wie Sie wissen, brauchen Sie bei statischen Mitgliedern keine Instanz einer Klasse, um sie verwenden zu können. Sie können daher die File.Copy-Methode benutzen, um eine beliebige Datei von einem Standort zu einem anderen zu kopieren. Das FileInfo-Objekt betrifft nur die aktuelle Instanz einer Datei. Die Methode FileInfo.Copy kopiert die aktuelle Datei. Die gleichen Regeln gelten für die Objekte Directory und DirectoryInfo. Meist werden Sie die Info-Objekte verwenden, da Sie mit spezifischen Dateien und Verzeichnissen umgehen.
380
Dateien und Verzeichnisse
Wir wollen uns ein Beispiel ansehen. Listing 11.1 erzeugt eine Verzeichnisliste im Explorer-Stil, indem es ein Verzeichnis durchläuft und die Elemente einem TreeView-Steuerelement hinzufügt. Listing 11.1: Eine Verzeichnisliste im Explorer-Stil erstellen 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42:
using using using using
System; System.Windows.Forms; System.Drawing; System.IO;
namespace TYWinforms.Day11 { public class Listing111 : Form { private TreeView tvFiles = new TreeView(); private Label lblInfo = new Label(); public Listing111() { tvFiles.Dock = DockStyle.Left; tvFiles.Width = 150; lblInfo.Size = new Size(100,100); lblInfo.Text = " Select a file or\ndirectory"; PopulateList("c: \\", tvFiles.Nodes.Add("c: \\")); this.Controls.Add(tvFiles); this.Controls.Add(lblInfo); } private void PopulateList(String strPath, TreeNode currNode) { DirectoryInfo dir = new DirectoryInfo(strPath); TreeNode nodeSubDir; foreach (DirectoryInfo d in dir.GetDirectories()) { nodeSubDir = currNode.Nodes.Add(d.Name); PopulateList(d.FullName, nodeSubDir); } foreach (FileInfo f in dir.GetFiles("*.*")) { currNode.Nodes.Add(f.Name); } } public static void Main() { Application.Run(new Listing111()); } } }
381
Arbeiten mit der Windows-Ein-/Ausgabe
Listing 11.1 beginnt normal: Ein Steuerelement ListView wird angelegt, mehrere Eigenschaften werden eingestellt und das Formular wird angezeigt. Doch die PopulateList-Methode ist etwas Neues. Die PopulateList-Methode wird als rekursive Methode bezeichnet, da sie immer wieder sich selbst aufruft. Sie beschafft Informationen über das im ersten Parameter angegebene Verzeichnis. Wird sie das erste Mal aufgerufen (wie in Zeile 18), ist das Verzeichnis C: bzw. das Stammverzeichnis. Zeile 26 legt ein temporäres Objekt an, das wir gleich verwenden. Das DirectoryInfo-Objekt in Zeile 25 enthält einige Informationen über das aktuelle Verzeichnis. Mit Hilfe der GetDirectories-Methode können Sie alle Unterverzeichnisse herausfinden und mit der GetFiles-Methode auch alle im Verzeichnis enthaltenen Dateien. In Zeile 28 iterieren Sie mit der foreachSchleife durch alle Unterverzeichnisse im aktuellen Verzeichnis. Zeile 29 fügt dem TreeView-Steuerelement einen neuen Knoten hinzu, wobei der Name jedes Unterverzeichnisses verwendet wird. Zeile 30 überspringen wir vorerst. Die FullName-Eigenschaft der Objekte DirectoryInfo und FileInfo liefert den Namen des Objektes einschließlich der kompletten Pfadangabe. Hier ein Beispiel: c:\inetpub\wwwroot\myfile.aspx
Die Name-Eigenschaft liefert hingegen nur den Namen ohne die Pfadangabe: myfile.aspx.
Zeile 33 ähnelt dem bereits Untersuchten. Zu jeder Datei im Verzeichnis fügen Sie der Strukturansicht einfach einen neuen Knoten mit dem jeweiligen Dateinamen hinzu. Nun ist das Dateisystem jedoch hierarchisch aufgebaut; man könnte Unterverzeichnisse bis zum Abwinken einrichten. Ohne Zeile 30 würde diese Methode nur die Unterverzeichnisse und Dateien des Stammverzeichnisses C:\ auflisten; sie geht nicht tiefer. Man braucht also eine Möglichkeit, immer tiefer durch die Unterverzeichnisse zu graben, bis man am Ende anlangt. Da man nicht wissen kann, wie tief die Verzeichnisstruktur auf der Festplatte eines Benutzers reicht, sollte man diese Unterverzeichnisse automatisch ermitteln können. Die PopulateList-Methode erledigt das für Sie. Enthält das aktuelle Verzeichnis keine Unterverzeichnisse, dann werden die Zeilen 28 bis 31 einfach nicht ausgeführt, und es geht weiter. Das Gleiche gilt für die Zeilen 33 bis 35. Es ist also sinnvoll, diese Methode auch für die nächste Ebene von Unterverzeichnissen einzusetzen. In Zeile 30 rufen Sie wieder die PopulateList-Methode auf, wobei Sie ihr die Pfadangabe des aktuellen Unterverzeichnisses und den Knoten im Hierarchiebaum übergeben. Wird die Methode diesmal ausgeführt, holt sie sich die Informationen über das Unterverzeich-
382
Dateien und Verzeichnisse
nis, fügt es unter dem aktuellen Knoten ein und durchläuft seine Unterverzeichnisse und Dateien wie sonst auch. Stößt der Vorgang auf ein weiteres Unterverzeichnis, wird dafür die PopulateList-Methode erneut aufgerufen und so weiter. Auf diese Weise können Sie alle Verzeichnisse und Dateien auf der Festplatte durchlaufen, ohne im Voraus wissen zu müssen, wie tief die Struktur reicht. Rekursion ist also für Ein- und Ausgabe von Dateien von hoher Bedeutung. Tabelle 11.1 erklärt den Ablauf ein bisschen detaillierter. Aktuelles Verzeichnis und aktueller Knoten
Übrige Unterverzeichnisse
Aktion
C:\
ASPNET, Windows
Durchlaufe das erste Unterverzeichnis in C:\
C:\ASPNET
Keine
Liste die Dateien in C:\ASPNET auf, falls sie verfügbar sind; gehe zurück zum übergeordneten Verzeichnis.
C:\
Windows
Durchlaufe das zweite Unterverzeichnis in C:\
C:\Windows
Wallpaper, System32
Durchlaufe das erste Unterverzeichnis in C:\Windows
C:\Windows\Wallpaper
Keine
Liste die Dateien in C:\Windows\Wallpaper auf, falls sie verfügbar sind; gehe zurück zum übergeordneten Verzeichnis.
C:\Windows
System32
Durchlaufe das zweite Unterverzeichnis in C:\Windows
C:\Windows\System32
Keine
Liste die Dateien in C:\Windows\System32 auf, falls sie verfügbar sind; gehe zurück zum übergeordneten Verzeichnis.
C:\Windows
Keine
Liste die Dateien in C:\Windows auf, falls sie verfügbar sind; gehe zurück zum übergeordneten Verzeichnis.
C:\
Keine
Liste die Dateien in C:\ auf, falls sie verfügbar sind; brich ab.
Tabelle 11.1: Der Ablauf der Rekursion
Abbildung 11.3 zeigt das Ergebnis dieser Anwendung. Beachten Sie, dass das Durchlaufen aller Verzeichnisse auf Ihrer Festplatte eine Weile dauern kann, so dass sich die Anwendung nicht sofort öffnet. Um diesen Vorgang zu beschleunigen, können Sie zur Laufzeit diese Baumstruktur erstellen lassen statt während der Initialisierung. Mit anderen Worten: Iterieren Sie durch ein Unterverzeichnis und seine Dateien nur dann, wenn der Benutzer
383
Arbeiten mit der Windows-Ein-/Ausgabe
diesen Knoten expandiert; so müssen Sie nur eine Ebene auf einmal bewältigen, und die Ausführung wird erheblich beschleunigt. Diese Aufgabe ist eine Übung für Sie.
Abbildung 11.3: Listing 11.1 erzeugt diese Explorer-ähnliche Anwendung.
Wenn Sie den Code in Listing 11.1 ausführen, könnten Sie eine Fehlermeldung erhalten, weil Sie nicht die Berechtigung haben, auf die Informationen aller Verzeichnisse und Dateien zuzugreifen. Sie können den Suchbereich auf ein bestimmtes Verzeichnis eingrenzen, indem Sie die GetDirectoriesMethode in Zeile 28 entsprechend ändern: .GetDirectories("windows*")
Diese Methode bringt die Anwendung dazu, nur solche Verzeichnisse aufzulisten, die das Wort windows im Namen aufweisen. Höchstwahrscheinlich lässt sich dadurch Ihr Berechtigungsproblem umgehen. An Tag 21 lernen Sie eine bessere Möglichkeit kennen. Wir wollen diese Anwendung erweitern, um den Umgang mit Datei- und Verzeichnisobjekten besser in den Griff zu bekommen. Fügen Sie in den Konstruktor folgenden Code ein: tvFiles.AfterSelect += new TreeViewEventHandler(this.DisplayInfo);
Wählt nun ein Benutzer einen Knoten in der Strukturansicht aus, können Sie ihm im Bezeichnungsfeld gemäß Zeile 9 von Listing 11.1 zusätzliche Informationen anzeigen. Listing 11.2 zeigt die DisplayInfo-Methode. Listing 11.2: Dem Benutzer Informationen anzeigen 1: 2: 3: 4: 5: 6:
384
private void DisplayInfo(Object Sender, TreeViewEventArgs e) { TreeNode tempNode = e.Node; String strFullName = tempNode.Text; while (tempNode.Parent != null) { strFullName = tempNode.Parent.Text + "\\" + strFullName;
Dateien und Verzeichnisse
7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21:
tempNode = tempNode.Parent; } if (File.Exists(strFullName)) { FileInfo obj = new FileInfo(strFullName); lblInfo.Text = "Name: " + obj.Name; lblInfo.Text += "\nGröße: " + obj.Length; lblInfo.Text += "\nZugriff: " + obj.LastAccessTime.ToString(); } else { DirectoryInfo obj = new DirectoryInfo(strFullName); lblInfo.Text = "Name: " + obj.Name; lblInfo.Text += "\nAttribute: " + obj.Attributes.ToString(); lblInfo.Text += "\nZugriff: " + obj.LastAccessTime.ToString(); } }
Das TreeViewNodeEventArgs-Objekt liefert dasjenige TreeNode-Objekt, das angeklickt wurde. In Zeile 2 speichern Sie dieses Objekt in einer Variablen zur späteren Verwendung. Dann holen Sie die Beschriftung in Zeile 3. Als Nächstes müssen Sie den vollen Pfadnamen desjenigen Knotens suchen, der angeklickt wurde, so dass Sie die Datei oder das Verzeichnis korrekt ermitteln können, um die entsprechenden Informationen hierzu anzuzeigen. Um den Pfadnamen zu suchen, verwenden Sie eine while-Schleife, die die ParentEigenschaft des aktuellen Knotens prüft. Gibt es keine übergeordneten Knoten (das wäre im Stammverzeichnis der Fall), wird die Parent-Eigenschaft den Wert null liefern und Sie wissen, dass Sie den vollen Pfadnamen haben. In der Schleife hängen Sie einfach den Namen jedes übergeordneten Knotens in die Zeichenfolge ein und bewegen sich eine Stufe höher. Der nächste Codeabschnitt bestimmt das zu behandelnde Objekt, sei es nun eine Datei oder ein Verzeichnis. Die File.Exists-Methode bestimmt, ob der angegebene Pfad auf eine Datei zeigt. Ist dies der Fall, zeigen Sie diverse Informationen über die Datei im Bezeichnungsfeld an, wie in den Zeilen 11 bis 14 zu sehen. Existiert die Datei jedoch nicht, wissen Sie, dass es sich statt dessen um ein Verzeichnis handelt, so dass Sie entsprechend andere Informationen anzeigen (Zeilen 16 bis 19). Werfen Sie nun einen weiteren Blick auf die Applikation, wie sie in Abbildung 11.4 zu sehen ist. Tabelle 11.2 führt die Mitglieder des FileInfo-Objekts auf.
385
Arbeiten mit der Windows-Ein-/Ausgabe
Abbildung 11.4: In Ihrem Explorer können Sie auch Rückmeldungen anbieten. Eigenschaft
Beschreibung
Attributes
Liefert die Dateiattribute. Kann einen der Werte der FileAttributes-Aufzählung annehmen: Archive, Compressed, Device, Directory, Encrypted, Hidden, Normal, NonContentIndexed, Offline, ReadOnly, ReparsePoint, SparseFile, System, Temporary.
CreationTime
Gibt Erstellungsdatum und –zeit einer Datei an.
Directory
Gibt ein DirectoryInfo-Objekt an, das das übergeordnete Verzeichnis dieser Datei darstellt.
DirectoryName
Enthält eine Zeichenfolge, die den vollständigen Pfad des übergeordneten Verzeichnisses angibt.
Exists
Gibt an, ob die Datei gültig ist.
Extension
Legt die Dateierweiterung fest.
FullName
Gibt den Dateinamen inklusive Pfadangabe an.
LastAccessTime
Gibt Datum und Zeit des letzten Dateizugriffs an.
LastWriteTime
Gibt Datum und Zeit des letzten Schreibzugriffs an.
Length
Gibt die Dateigröße an.
Name
Gibt den Dateinamen exklusive Pfadangabe an.
Methode
Beschreibung
AppendText
Erzeugt einen StreamWriter, mit dem man Text an die Datei anhängen kann.
Tabelle 11.2: FileInfo-Eigenschaften
386
Dateien und Verzeichnisse
Eigenschaft
Beschreibung
CopyTo
Kopiert die Datei an einen neuen Speicherplatz.
Create
Legt die Datei an.
CreateText
Legt die Datei und einen StreamWriter an, der in die Datei schreiben kann.
Delete
Löscht die Datei.
MoveTo
Verschiebt die Datei an einen neuen Speicherort.
Open
Öffnet die Datei mit diversen Berechtigungen.
OpenRead
Erzeugt für diese Datei ein schreibgeschütztes FileStream-Objekt.
OpenText
Erzeugt für diese Datei ein FileStream-Objekt mit Lese-/Schreibzugriff.
Tabelle 11.2: FileInfo-Eigenschaften (Forts.)
Tabelle 11.3 führt die verschiedenen Mitglieder des DirectoryInfo-Objekts auf. Ihnen fällt sicher auf, dass es einige Eigenschaften mit dem FileInfo-Objekt gemeinsam hat. Eigenschaft
Beschreibung
Attributes
Liefert die Verzeichnisattribute. Kann einen der Werte der FileAttributesAufzählung annehmen: Archive, Compressed, Device, Directory, Encrypted, Hidden, Normal, NonContentIndexed, Offline, ReadOnly, ReparsePoint, SparseFile, System, Temporary.
CreationTime
Gibt Erstellungszeit und -datum für das Verzeichnis an.
Exists
Gibt an, ob dieses Verzeichnis gültig ist.
Extension
Gibt die Namenserweiterung des Verzeichnisses an.
FullName
Gibt den Verzeichnisnamen inklusive Pfadangabe an.
LastAccessTime
Gibt Datum und Zeit des letzten Verzeichniszugriffs an.
LastWriteTime
Gibt Datum und Zeit des letzten Schreibzugriffs auf das Verzeichnis an.
Name
Gibt den Verzeichnisnamen exklusive Pfadangabe an.
Parent
Liefert ein DirectoryInfo-Objekt, das das übergeordnete Verzeichnisses dieses Verzeichnisses darstellt.
Root
Liefert ein DirectoryInfo-Objekt, das die Wurzel dieses Verzeichnisses darstellt.
Tabelle 11.3: Die Mitglieder von DirectoryInfo
387
Arbeiten mit der Windows-Ein-/Ausgabe
Eigenschaft
Beschreibung
Methode
Beschreibung
Create
Erstellt das Verzeichnis.
CreateSubDirectory
Erstellt das Verzeichnis und liefert ein DirectoryInfo-Objekt, das das neue Verzeichnis darstellt.
Delete
Löscht das Verzeichnis.
GetDirectories
Holt ein Array von DirectoryInfo-Objekten, die alle Unterverzeichnisse des aktuellen Verzeichnisses darstellen.
GetFiles
Holt ein Array von FileInfo-Objekten, die alle Dateien des aktuellen Verzeichnisses darstellen.
GetFileSystemInfos
Holt ein Array von FileSystemInfo-Objekten, die alle Dateien und Verzeichnisse darstellen.
MoveTo
Verschiebt die Datei an einen neuen Speicherplatz.
Tabelle 11.3: Die Mitglieder von DirectoryInfo (Forts.)
Dateien lesen und schreiben Gleichgültig, ob die Datei, die Sie verarbeiten wollen, nun binär ist oder aus reinem Text besteht, so bleibt doch die Vorgehensweise beim Lesen und Schreiben die gleiche: Man hängt ein Streamobjekt des erforderlichen Typs an und hängt diesem wiederum einen Reader oder Writer zwecks Dateizugriff an. Darüber hinaus haben alle Reader und Writer die gleichen allgemeinen Mitglieder – wenn Sie wissen, wie Sie eines verwenden, können Sie alle verwenden. Wir wollen uns ein Beispiel für das Schreiben einer Textdatei ansehen. Listing 11.3 zeigt eine einfache Anwendung, in die der Benutzer einen Dateinamen zwecks Erstellung eingeben kann. Diese Textdatei wird mit dem Text "Hello World!" darin erstellt. Listing 11.3: Eine Datei erzeugen 1: 2: 3: 4: 5: 6: 7:
388
using using using using
System; System.Windows.Forms; System.Drawing; System.IO;
namespace TYWinforms.Day11 { public class Listing112 : Form {
Dateien und Verzeichnisse
8: private Button btCreate = new Button(); 9: private TextBox tbFileName = new TextBox(); 10: 11: public Listing112() { 12: tbFileName.Location = new Point(25,100); 13: tbFileName.Width = 150; 14: 15: btCreate.Text = "Erstellen!"; 16: btCreate.Location = new Point(200,100); 17: btCreate.Click += new EventHandler(this.CreateFile); 18: 19: this.Text = "Listing 11.2"; 20: this.Controls.Add(btCreate); 21: this.Controls.Add(tbFileName); 22: } 23: 24: public void CreateFile(Object Sender, EventArgs e) { 25: String strFilename = tbFileName.Text; 26: 27: if (!File.Exists(strFilename)) { 28: FileStream objFile = new FileStream(strFilename, FileMode.CreateNew); 29: StreamWriter objWriter = new StreamWriter(objFile); 30: 31: objWriter.WriteLine("Hello World!"); 32: 33: objWriter.Close(); 34: objFile.Close(); 35: 36: MessageBox.Show("Fertig!"); 37: } 38: } 39: 40: public static void Main() { 41: Application.Run(new Listing112()); 42: } 43: } 44: }
Wir wollen den Konstruktor in Listing 11.3 überspringen; das einzige Interessante daran ist die Zuweisung des Delegaten in Zeile 17. Alle Aktionen beginnen in der CreateFile-Methode ab Zeile 24. Dieser Code setzt voraus, dass der Benutzer etwas Text in die TextBox eingegeben hat. In Zeile 27 prüfen Sie, ob die angegebene Datei bereits existiert. Ist das der Fall, sollten Sie sie in Ruhe lassen. Gibt es sie noch nicht, dürfen Sie fortfahren.
389
Arbeiten mit der Windows-Ein-/Ausgabe
Zeile 28 legt ein FileStream-Objekt an, um mit der Datei zu interagieren. Der Konstruktor dafür übernimmt einige Parameter. Der erste ist natürlich der Pfad zu der fraglichen Datei. Der zweite ist optional und spezifiziert den Modus, mit dem die Datei geöffnet wird. Er kann einen der Werte der FileMode-Aufzählung annehmen: 왘
Append: Öffnet die Datei, falls sie existiert, und bewegt sich bis zum Ende der Datei. Ist sie jedoch nicht vorhanden, wird eine neue Datei angelegt.
왘
Create: Legt eine neue Datei an. Ist sie bereits vorhanden, wird sie überschrieben.
왘
CreateNew: Legt eine neue Datei an. Ist sie bereits vorhanden, wird eine
Fehlermeldung ausgegeben. 왘
Open: Öffnet eine vorhandene Datei.
왘
OpenOrCreate: Öffnet die Datei, falls sie vorhanden ist. Falls nicht, wird eine
neue Datei angelegt. 왘
Truncate: Öffnet eine vorhandene Datei und löscht jeden vorhandenen
Inhalt. Im Code legen Sie FileMode.CreateNew so fest, dass eine neue Datei unter Verwendung des in der TextBox angegebenen Pfadnamens angelegt wird. Nun da Sie für Ihre Datei einen Stream haben, müssen Sie einen Writer erstellen, der Daten in sie schreibt. Da wir mit Informationen in reinem Text umgehen, erstellen wir in Zeile 29 einen StreamWriter. Der Konstruktor dieses Objekts übernimmt nur einen Parameter, nämlich den Stream, dem der Writer zuzuordnen ist. Die WriteLine-Methode in Zeile 31 schreibt den angegebenen Text in den Stream (und daher in die Datei) und hängt ein Zeilenabschlusszeichen an das Ende der Zeichenfolge an. Die Write-Methode hingegen hängt kein Zeichen für den Zeilenabschluss an. Schließlich müssen Sie daran denken, die Close-Methoden sowohl für den Writer als auch den Stream aufzurufen, damit die Anwendung weiß, dass Sie damit fertig sind, und sie daher wertvolle Systemressourcen freigeben kann. In Zeile 36 wird ein Meldungsfeld angezeigt, um dem Benutzer mitzuteilen, dass die Aufgabe erledigt ist. Kompilieren Sie nun die Anwendung und führen Sie sie aus. Tragen Sie einen gültigen Pfadnamen ein (z.B. C:\temp\blubb.txt) und klicken Sie auf die Schaltfläche ERSTELLEN. Wenn Sie in das angegebene Verzeichnis wechseln, sehen Sie eine neue Textdatei, die die Wörter "Hello World" enthält.
390
Dateien und Verzeichnisse
Lassen Sie uns für einen Moment zurückgehen und uns den Konstruktor für das Streamobjekt ansehen. Dieser flexible Konstruktor kann alle möglichen Parameter übernehmen. So sind z.B. alle folgenden Zeilen gültig: new FileStream(strFilename, new FileStream(strFilename, new FileStream(strFilename, new FileStream(strFilename, FileShare.None,1024,true);
FileMode.CreateNew); FileMode.Create, FileAccess.Read); FileMode.Create, FileAccess.Read, FileShare.None); FileMode.Create, FileAccess.Read,
Den ersten Konstruktor kennen Sie bereits. Der zweite erlaubt Ihnen die Funktionen festzulegen, die der Stream an der Datei ausführen kann. Dabei können Sie die Werte der Aufzählung FileAccess nutzen, die Read, ReadWrite und Write lauten. Der dritte Konstruktor fügt dieser Liste einen weiteren Aufzählungswert aus FileShare hinzu. Dieser Wert bestimmt, wie andere Streamobjekte zur gleichen Zeit wie Sie auf diese Datei zugreifen können. Die Werte können None, Read, ReadWrite oder Write sein. Ein FileShare.None-Wert bedeutet, dass anderen Streams der Zugriff auf diese Datei verwehrt wird, während Sie mit ihr arbeiten. Der letzte Konstruktor fügt zwei weitere Parameter hinzu. Der erste zeigt die Puffergröße in Byte an, die für die Datei zu verwenden ist. Das Setzen eines Pufferspeichers kann die Performanz verbessern. Der zweite Parameter gibt an, ob die Arbeitsschritte asynchron ausgeführt werden sollen; dazu mehr im Unterkapitel »Asynchrone Ein-/Ausgabe«. Die Erstellung des Streamobjekts ist der komplizierteste Teil des Listings 11.3. Doch die Verwendung des Writers ist einfach, denn man kann nur zwischen zwei Methoden wählen: Write oder WriteLine. Wir wollen diesem Listing einen Reader hinzufügen. Zu diesem Zweck modifizieren wir den if-Block in Zeile 27 des Listings 11.3 so, dass er ein else-Statement umfasst: } else { FileStream objFile = new FileStream(strFilename, FileMode.Open, FileAccess.Read); StreamReader objReader = new StreamReader(objFile);
String strContents = objReader.ReadToEnd(); objReader.Close(); objFile.Close(); MessageBox.Show(strContents); }
391
Arbeiten mit der Windows-Ein-/Ausgabe
In diesem Fall ist bekannt, dass die Datei existiert. Sie erzeugen wie zuvor ein FileStreamObjekt und benutzen die gleiche Pfadangabe. Statt eines StreamWriters erzeugen Sie jedoch diesmal einen StreamReader. Die Syntax ist identisch. Als Nächstes rufen Sie die Methode ReadToEnd des Readers auf, um den gesamten Dateiinhalt zurückzugeben. Dann schließen Sie Reader und Stream und zeigen den Inhalt der Datei in einem Meldungsfeld an. Führen Sie nun die Anwendung erneut aus, geben Sie den zuvor benutzten Dateinamen an und klicken Sie auf die Schaltfläche ERSTELLEN. Statt einer »Erledigt!«-Meldung erscheint nun ein Meldungsfeld mit dem entsprechenden Dateiinhalt. Bearbeiten Sie diese Datei manuell und versuchen Sie es erneut. Im Meldungsfeld erscheint nun der neue Inhalt. Abbildung 11.5 zeigt ein Beispiel für ein mögliches Ergebnis.
Abbildung 11.5: Bei Verwendung von Streams können Sie Textdateien leicht lesen und schreiben.
Das StreamReader-Objekt verfügt über eine größere Anzahl Methoden für das Lesen von Streams, als der StreamWriter für das Schreiben von Streams. Ihnen ist bereits die Methode ReadToEnd begegnet, die den Gesamtinhalt einer Datei liefert. ReadLine liefert jeweils nur eine Zeile dieser Datei, ReadBlock liest eine festgelegte Menge von Zeichen, die an einem bestimmten Punkt beginnt, und legt die zurückgegebenen Daten in einem Array ab. Der Ausdruck ReadBlock(buffer, 0, 100) etwa liest vom ersten bis zum 100. Zeichen und legt diese Menge im char-Array buffer() ab. Erreichen Sie das Dateiende vor dem 100. Zeichen, liefert die Methode einfach nur das, was verfügbar ist. Die einfache Read-Methode liest aus dem Stream nur jeweils ein Zeichen. Ein Zeiger wird zum nächsten Zeichen bewegt, sobald Sie diese Methode aufrufen, so dass das nächste Zeichen beim nächsten Read-Aufruf geliefert wird. Read kann sich ebenfalls wie die ReadBlockMethode verhalten, wenn Sie ihr ein char-Array sowie Anfangs- und Endwerte übergeben. Die Peek-Methode ähnelt wiederum Read: Sie holt das nächste Zeichen aus dem Stream, verschiebt aber den Zeiger nicht. Diese Methode wird am häufigsten dazu benutzt, um festzustellen, ob der fragliche Stream noch weiteren Inhalt aufweist. Ist das nicht der Fall, liefert Peek den Wert –1.
392
Dateien und Verzeichnisse
Es stehen Ihnen zahlreiche Typen von Readern und Writern zur Verfügung. Bislang haben Sie nur StreamReader und StreamWriter in Aktion gesehen, die Text lesen/schreiben können. In gleicher Weise können StringReader und StringWriter Zeichenfolgen lesen/ schreiben. HttpWriter kann Ausgaben an einen Web-Browser schreiben. In ASP.NET verwenden Sie HtmlTextWriter, um HTML auch in Browsern wiederzugeben. BinaryReader und BinaryWriter lesen und schreiben Binärdaten. Schlagen Sie in der Dokumentation zum .NET Framework nach, um mehr Informationen über diese Reader und Writer zu finden. Da Sie aber bereits StreamReader und StreamWriter kennen, wissen Sie, wie die meisten funktionieren.
Binärdaten lesen und schreiben Die zu lesenden Dateien kommen nicht immer im Textformat daher; vielmehr enthalten sie häufig Binärdaten. Diese können Sie nicht ohne die Hilfe eines Computers lesen. Beispielswiese verwenden Datenbanken häufig das Binärformat, um Platz zu sparen und ihre Inhalte vor neugierigen Blicken zu schützen. Doch keine Sorge: .NET ist vollständig in der Lage, selbst Binärdaten zu lesen und zu schreiben. Und Sie wissen bereits, wie das geht. Wir wollen eine Beispielanwendung erstellen, die Binärdaten lesen und schreiben kann. Sie ist in Listing 11.4 zu sehen. Listing 11.4: Im Binärformat arbeiten 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20:
Imports Imports Imports Imports
System System.Windows.Forms System.Drawing System.IO
Namespace TYWinForms.Day11 Class Listing114 : Inherits Form private btWriteIt As New Button() private btReadIt As New Button() private nudHigh As New NumericUpDown() private nudLo As New NumericUpDown() private dtpDate As New DateTimePicker() private lblHigh As New Label() private lblLo As New Label() Public Sub New() lblLo.Text = "Tiefe Temp" lblLo.Location = new Point(10,10) nudLo.Location = new Point(110,10)
393
Arbeiten mit der Windows-Ein-/Ausgabe
21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65:
394
nudLo.Minimum = -100 nudLo.Maximum = 130 lblHigh.Text = "Hohe Temp" lblHigh.Location = new Point(10,40) nudHigh.Location = new Point(110,40) nudHigh.Minimum = -100 nudHigh.Maximum = 130 dtpDate.Location = new Point (10,80) btWriteIt.Text = "Schreiben!" btWriteIt.Location = new Point(30,200) AddHandler btWriteIt.Click, new EventHandler(AddressOf Me.WriteIt) btReadIt.Text = "Lesen!" btReadIt.Location = new Point(120,200) AddHandler btReadIt.Click, new EventHandler(AddressOf Me.ReadIt) Me.Controls.Add(lblLo) Me.Controls.Add(nudLo) Me.Controls.Add(lblHigh) Me.Controls.Add(nudHigh) Me.Controls.Add(dtpDate) Me.Controls.Add(btWriteIt) Me.Controls.Add(btReadIt) End Sub private sub WriteIt(Sender As Object, e As EventArgs) Dim objStream As New FileStream("c: \winforms\day11\records.dat", FileMode.Create) Dim objWriter As New BinaryWriter(objStream) objWriter.Write(CType(nudLo.Value, Short)) objWriter.Write(CType(nudHigh.Value, Short)) objWriter.Write(dtpDate.Value.ToString("d")) objWriter.Close() objStream.Close() end sub private sub ReadIt(Sender As Object, e As EventArgs) Dim strTemp As String Dim lo, hi as Short if not File.Exists("c:\winforms\day11\records.dat") then
Dateien und Verzeichnisse
66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80: 81: 82: 83: 84: 85: 86: 87:
MessageBox.Show("Die Datei existiert nicht!") Return End If Dim objStream = New FileStream("c:\winforms\day11\records.dat", FileMode.Open, FileAccess.Read) Dim objReader As New BinaryReader(objStream) lo = objReader.ReadInt16() hi = objReader.ReadInt16() strTemp = objReader.ReadString() MessageBox.Show("Höchst- und Tiefsttemperaturen am " & strTemp & " waren " & hi.ToString() & " und " & lo.ToString() ) objReader.Close() objStream.Close() end sub Public Shared Sub Main() Application.Run(new Listing114()) End Sub End Class End Namespace
Dieses Programm holt die Höchst- und Tiefsttemperaturen eines Tages ein und schreibt diese Werte zusammen mit dem Datum in eine Datei. Neben den Schaltflächen für das Schreiben und Lesen einer Datei deklarieren wir weitere Steuerelemente für die Eingabe von Temperaturen und des Datums (Zeilen 9 bis 15). Die Steuerelemente werden initialisiert und im Konstruktor unserem Formular hinzugefügt (Zeilen 18 bis 46). Die WriteIt- und ReadIt-Methoden in den Zeilen 49 bzw. 61 bilden die Ereignishandler für unsere zwei Schaltflächen, die eine neue Binärdatei erzeugen und aus derselben Datei lesen. Mit der WriteIt-Methode beginnt das Anlegen eines FileStream-Objekts in Zeile 50 – nichts Neues für Sie. Auch Zeile 51 ist ähnlich, doch Sie erzeugen statt eines StreamWriters einen BinaryWriter. In den Zeilen 53 bis 55 schreiben Sie im Binärformat die Werte Ihrer Steuerelemente in Ihre Datei. Wir verwenden CType, um die Temperaturen in einen Short-Datentyp zu konvertieren, der weniger Platz beansprucht. Dann benutzen wir einen der DateTime-Formatierungscodes (vgl. Tag 8), um unser Datum in eine Zeichenfolge mit kurzem Datumsmuster (mm/dd/yyyy) umzuwandeln. Die BinaryWriter.Write-Methode wird für mehrere Datentypen überladen, so dass die Aufrufe in den Zeilen 53
395
Arbeiten mit der Windows-Ein-/Ausgabe
und 54 diejenige Version der Methode aufrufen, die einen Short-Datentyp übernimmt, wohingegen der Aufruf in Zeile 55 die String-Version von Write aufruft. In den Zeilen 57 und 58 schließen Sie Writer- und Streamobjekte. Um ein Zwischenergebnis zu erhalten, wollen wir einmal diese Anwendung ausführen und auf die Schaltfläche SCHREIBEN! klicken. Gehen Sie nach der Programmausführung ins Verzeichnis c:\winforms\day11, wo Sie eine neue Datei namens records.dat vorfinden sollten. Öffnen Sie die Datei mit dem Windows-Editor, um folgende Ausgabe vorzufinden: ??????????????
Der Ausgabevorgang weist durchaus keinen Fehler auf. Der Editor kann nur reinen Text verarbeiten und wenn er auf diese Ausgabe im Binärformat stößt, die Sie erstellt haben, weiß er sich nicht anders als mit der Darstellung von Fragezeichen zu helfen. Um Ihnen zu beweisen, dass dies kein Trick ist, kehren Sie zu Listing 11.4 zurück. In Zeile 65 prüfen Sie in der Methode ReadIt, ob diese Datei existiert (sie sollte), doch ist dies nicht der Fall, hört die Subroutine (in Zeile 67) mit der Verarbeitung auf. In Zeile 70 erzeugen Sie ein weiteres FileStream-Objekt für die Datei records.dat und in Zeile 71 einen BinaryReader. Da wir wissen, dass wir gerade zwei Shorts und einen nachfolgenden String geschrieben haben, um die Daten auszugeben, werden wir in den Zeilen 73 bis 75 zwei Shorts und einen String lesen, um die Daten wieder einzulesen. Doch beim Lesen von Binärdaten müssen wir eine andere Methode aufrufen. Da ein Short in VB .NET ein 16-Bit-Integer ist, verwenden wir die Methode ReadInt16 für das Short-Lesen und die Methode ReadString für das String-Lesen. Den sich daraus ergebenden Text zeigen wir in Zeile 77 an und schließen dann (Zeilen 80/81) den Reader und den Stream. Wenn Sie diese Anwendung ausführen und auf die Schaltfläche LESEN! klicken, erscheint ein Meldungsfenster mit etwa folgendem Text: Höchst- und Tiefsttemperaturen am 28.8.2002 waren 44 und 22.
Ihr Binärreader hat also erfolgreich das Binärformat in reinen Text übersetzt. Wenn Sie mit Binärdateien umgehen, ist es von höchster Bedeutung zu wissen, wie deren Inhalt jeweils aufgebaut ist, damit Sie sie richtig auslesen können. Sonst könnten die Ergebnisse unvorsehbar sein.
Serialisierung .NET stellt auch eine Methode bereit, um Objekte zu serialisieren. Serialisierung ist der Vorgang, bei dem der Zustand einer Objektinstanz auf ein Speichermedium übertragen wird. Wenn Sie also etwas serialisieren, dann wandeln Sie es in ein Format um, das sich leicht auf Festplatte speichern oder über das Internet übertragen lässt. Wird ein Objekt serialisiert, speichert man nicht nur
396
Dateien und Verzeichnisse
die public- und private-Mitglieder des Objekts, sondern auch die Metadaten der Klasse selbst. Auf diese Weise kann ein anderes Programm, eventuell auf einem anderen Rechner, diese Daten bei der Deserialisierung zurücklesen und eine exakte Kopie des Originalobjekts erzeugen. Doch die Probleme werden Ihnen auch diesmal in .NET von der CLR abgenommen und daher lässt sich Serialisierung leicht implementieren. Das folgende Beispiel übernimmt die gleichen Daten, die in Listing 11.4 geschrieben und gelesen wurden, doch diesmal wandeln wir diese Daten in eine Klasse um und überlassen der Serialisierung von .NET das Schreiben und Lesen. Listing 11.5: Serialisierung von Binärdaten 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32:
Imports Imports Imports Imports Imports Imports
System System.Windows.Forms System.Drawing System.IO System.Runtime.Serialization System.Runtime.Serialization.Formatters.Binary
Namespace TYWinForms.Day11 <Serializable()> Class TempRec public loTemp as Short public hiTemp as Short public dateTemp as DateTime End Class Class Listing115 : Inherits Form private btWriteIt As New Button() private btReadIt As New Button() private nudHigh As New NumericUpDown() private nudLo As New NumericUpDown() private dtpDate As New DateTimePicker() private lblHigh As New Label() private lblLo As New Label() Public Sub New() lblLo.Text = "Tiefe Temp" lblLo.Location = new Point(10,10) nudLo.Location = new Point(110,10) nudLo.Minimum = -100 nudLo.Maximum = 130 lblHigh.Text = "Hohe Temp"
397
Arbeiten mit der Windows-Ein-/Ausgabe
33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76:
398
lblHigh.Location = new Point(10,40) nudHigh.Location = new Point(110,40) nudHigh.Minimum = -100 nudHigh.Maximum = 130 dtpDate.Location = new Point (10,80) btWriteIt.Text = "Schreiben!" btWriteIt.Location = new Point(30,200) AddHandler btWriteIt.Click, new EventHandler(AddressOf Me.WriteIt) btReadIt.Text = "Lesen!" btReadIt.Location = new Point(120,200) AddHandler btReadIt.Click, new EventHandler(AddressOf Me.ReadIt) Me.Controls.Add(lblLo) Me.Controls.Add(nudLo) Me.Controls.Add(lblHigh) Me.Controls.Add(nudHigh) Me.Controls.Add(dtpDate) Me.Controls.Add(btWriteIt) Me.Controls.Add(btReadIt) End Sub private sub WriteIt(Sender As Object, e As EventArgs) Dim objStream As New FileStream("c:\winforms\day11\records.dat", FileMode.Create) Dim objFormatter As New BinaryFormatter() Dim objTempREc As New TempRec() objTempRec.loTemp = nudLo.Value objTempRec.hiTemp = nudHigh.Value objTempRec.dateTemp = dtpDate.Value objFormatter.Serialize(objStream,objTempRec) objStream.Close() end sub private sub ReadIt(Sender As Object, e As EventArgs) if not File.Exists("c:\winforms\day11\records.dat") then MessageBox.Show("Diese Datei existiert nicht!") Return End If
Dateien und Verzeichnisse
77: 78: 79: 80: 81: 82: 83:
84: 85: 86: 87: 88: 89: 90: 91: 92:
Dim objStream = New FileStream("c:\winforms\day11\records.dat", FileMode.Open, FileAccess.Read) Dim objFormatter As New BinaryFormatter() Dim objTempREc As New TempRec() objTempRec = objFormatter.Deserialize(objStream) MessageBox.Show("Höchst- und Tiefsttemperaturen am " & objTempRec.dateTemp.ToString("d") & " waren " & objTempRec.hiTemp. ToString() & " und " & objTempRec.loTemp.ToString() ) objStream.Close() end sub Public Shared Sub Main() Application.Run(new Listing115()) End Sub End Class End Namespace
In den Zeilen 5 und 6 importieren wir die Bibliotheken, die wir für die Implementierung der Serialisierung benötigen. In Zeile 10 erzeugen wir eine neue Klasse namens TempRec und geben ihr das Attribut Serializable. Dieses Attribut bringt .NET dazu, unserer Klasse automatisch die Serialisierungsfunktionalität bereitzustellen. In C# würde das Attribut wie folgt aussehen: [Serializable] class TempRec
Wir halten die Klasse sehr einfach, so dass sie nur drei öffentliche Felder enthält, die unsere Daten aufnehmen sollen. In Zeile 59 deklarieren wir in unserer WriteIt-Methode ein Objekt vom Typ BinaryFormatter, wo wir zuvor BinaryWriter verwendet hatten. In Zeile 60 deklarieren wir ein Objekt unseres TempRec-Typs und initialisieren es in den Zeilen 62 und 63 mit Werten aus unseren Steuerelementen. In Zeile 66 rufen wir die Serialize-Methode unseres BinaryFormatter-Objekts auf und übergeben ihr als Parameter unser FileStream-Objekt und unser TempRec-Objekt. Das BinaryFormatter-Objekt weiß, wie es den Zustand unseres Objekts im Stream speichern soll. Das beweisen wir uns selbst in unserer ReadIt-Methode, in der wir in den Zeilen 78 und 79 wieder ein BinaryFormatter-Objekt und ein TempRec-Objekt deklarieren. In Zeile 81 lesen wir unser Objekt aus der Datei zurück, wobei wir die Deserialize-Methode des BinaryFormatter-Objekts verwenden. In Zeile 83 geben wir die Ergebnisse aus.
399
Arbeiten mit der Windows-Ein-/Ausgabe
Worin besteht also der Sinn der Übung? Sie soll zeigen, dass in .NET nicht nur Daten übertragbar sind, sondern auch Objekte und Klassen. Nun können Sie die Datei records.dat an jeden Adressaten schicken: Sie werden nicht nur die gespeicherten Temperaturdaten erhalten, sondern auch die Klasse, die die Daten enthält. An Tag 15 erfahren Sie etwas über XML-Serialisierung: Statt Objekte in Binärdaten umzuwandeln, werden sie in XML transformiert. Mehr über Serialisierung finden Sie in der .NET Framework Dokumentation.
Das Steuerelement RichTextBox Nun gibt es ein Wiedersehen mit dem Steuerelement RichTextBox (vgl. Tag 6). Damit kann der Benutzer Text wie in einem Textverarbeitungsprogramm eingeben. Sie haben aber noch nicht seine Fähigkeit gesehen, Dateien zu lesen und zu schreiben. Listing 11.6 ist eine vereinfachte Form von Listing 6.9, die das Steuerelement RichTextBox verwendet. Listing 11.6: Wieder mal das Steuerelement RichTextBox 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23:
400
Imports System Imports System.Windows.Forms Imports System.Drawing Namespace TYWinForms.Day11 Public Class Listing116 : Inherits Form private rtbText as New RichTextBox public sub New() rtbText.Dock = DockStyle.Fill rtbText.ScrollBars = RichTextBoxScrollBars.Both Me.Text = "Listing 11.6" Me.Font = New Font("Times New Roman", 10) Me.Controls.Add(rtbText) end sub public shared sub Main() Application.Run(new Listing116) end sub End Class End Namespace
Dateien und Verzeichnisse
Mittlerweile dürfte Ihnen der Code in Listing 11.6 vertraut sein, so dass wir diesmal die Analyse überspringen können. Die zwei Methoden, die Ihnen noch nicht begegnet sind, heißen LoadFile und SaveFile. Die Methode LoadFile übernimmt den Inhalt entweder einer Datei oder eines Stroms und legt ihn in einem RichTextBox-Steuerelement ab, damit ihn der Benutzer bearbeiten kann. So können Sie etwa folgenden Code in den Konstruktor von Listing 11.6 einfügen (der Pfad muss auf eine existente Datei Ihres Computers verweisen): rtbText.LoadFile("c:\temp\happy.text", RichTextBoxStreamType.PlainText)
Der erste Parameter ist die zuvor erstellte Datei (entweder eine Pfadangabe oder ein Stream), und der zweite teilt dem RichTextBox-Steuerelement mit, welchen Typ von Datei es erwarten soll. Die möglichen Werte der RichTextBoxStreamType-Aufzählung sind PlainText, RichNoOleObjs (ein RTF-Stream mit Leerzeichen anstelle von OLE-Objekten), RichText, TextTextOleObjs (ein Stream aus einfachem Text mit einer Textdarstellung von OLE-Objekten) und UnicodePlainText. (OLE-Objekte besprechen wir an Tag 14.) Abbildung 11.6 zeigt die neue Ausgabe der Anwendung, der die LoadFile-Methode hinzugefügt wurde.
Abbildung 11.6: Die LoadFile-Methode kann jede textbasierte Datei in ein RichTextBox-Steuerelement laden.
Die SaveFile-Methode übernimmt exakt die gleichen Parameter und ermöglicht Ihnen das Schreiben oder Erzeugen einer Datei. Jetzt wissen Sie, dass das Steuerelement RichTextBox über E/A-Fähigkeiten verfügt.
401
Arbeiten mit der Windows-Ein-/Ausgabe
11.3 Das Objekt FileSystemWatcher Falls Sie Ihren Computer hinsichtlich Änderungen an Dateien oder Verzeichnissen (Ordnern) überwachen müssen, dann ist FileSystemWatcher das Objekt Ihrer Wahl. Es steht sozusagen Wache und beobachtet alles, was in Ihrem System vor sich geht. Passiert etwas, dann löst das Objekt ein Ereignis aus, dem Sie einen Delegaten zuweisen können, damit die notwendigen Aktionen erfolgen. Angenommen, Sie haben eine Anwendung entwickelt, die eine Verzeichnisliste anlegt (vgl. Listing 11.1). Fügt ein Benutzer neue Dateien hinzu oder löscht Ordner, dann wird die Strukturansicht (TreeView) nicht aktualisiert, um die Änderungen widerzuspiegeln. Mit dem FileSystemWatcher-Objekt können Sie jedoch das TreeView-Steuerelement benachrichtigen, dass solche Änderungen vorgenommen wurden, und je nach Bedarf Knoten hinzufügen oder entfernen. Um einen FileSystemWatcher einzurichten, müssen Sie die zu überwachenden Dateien und Ordner sowie die zu verfolgenden Änderungen angeben und dann das Objekt aktivieren. Das folgende Codestück erledigt alle drei Aufgaben: FileSystemWatcher objWatcher = new FileSystemWatcher(); //geben Sie die zu überwachenden Dateien und Verzeichnisse an objWatcher.Path = "c:\"; objWatcher.Filter = "*.txt "; objWatcher.IncludeSubdirectories =true; //geben Sie die zu überwachenden Änderungen an objWatcher.NotifyFilter = NotifyFilters.LastWrite; //schalten Sie den Beobachter ein objWatcher.EnableRaisingEvents = true;
Die ersten drei Eigenschaften sind praktisch selbsterklärend. Path ist das zu beobachtende Verzeichnis, Filter der zu beobachtende Dateityp in diesem Verzeichnis (also alle Dateien mit der Erweiterung *.txt) und IncludeSubDirectories gibt an, ob auch Unterverzeichnisse überwacht werden sollen. Die nächste Eigenschaft, NotifyFilter, übernimmt Werte aus der NotifyFilter-Aufzählung. Diese lassen sich mit dem Operator | (OR in VB .NET) kombinieren. Dies können folgende Werte sein: 쐽
Attributes: Zeigt an, dass sich eine Änderung an den Datei- oder Verzeichnisattributen ereignet hat.
쐽
CreationTime: Zeigt an, dass die Erstellungszeit einer Datei oder eines Ordners geän-
dert wurde (das jeweilige Objekt wurde also erstellt).
402
Das Objekt FileSystemWatcher
쐽
DirectoryName: Zeigt, dass sich der Ordnername geändert hat.
쐽
FileName: Zeigt, dass sich der Dateiname geändert hat.
쐽
LastAccess: Zeigt an, dass auf eine Datei oder einen Ordner zugegriffen wurde.
쐽
LastWrite: Zeigt an, dass in eine Datei geschrieben wurde.
쐽
Security: Zeigt an, dass man die Sicherheitseigenschaften einer Datei oder eines Ordners geändert hat.
쐽
Size: Gibt an, dass sich die Größe einer Datei oder eines Ordners geändert hat.
쐽
Zum Schluss setzen Sie einfach EnableRaisingEvents auf true, um den Beobachter zu aktivieren. Der FileSystemWatcher verfügt über vier Ereignisse: Changed, Created, Deleted und Renamed. Diesen können Sie jeweils einen Delegaten zuweisen, der eine Art der Bearbeitung darstellt. Wir wollen ein einfaches Beispiel erstellen. Listing 11.7 erzeugt eine Anwendung, die auf Ihrem Windows-Desktop erscheinen soll. Sobald sich etwas in Ihrem Dateisystem ändert, löst diese Anwendung einen Alarm aus.
Listing 11.7: Ihre Festplatte überwachen 1: using System; 2: using System.Windows.Forms; 3: using System.Drawing; 4: using System.IO; 5: 6: namespace TYWinforms.Day11 { 7: public class Listing117 : Form { 8: FileSystemWatcher objWatcher = new FileSystemWatcher(); 9: 10: public Listing117() { 11: objWatcher.Path = "c:\\temp"; 12: objWatcher.Filter = "*.*"; 13: objWatcher.IncludeSubdirectories = true; 14: objWatcher.NotifyFilter = NotifyFilters.CreationTime | NotifyFilters.LastWrite | NotifyFilters.Size; 15: objWatcher.EnableRaisingEvents = true; 16: 17: objWatcher.Changed += new FileSystemEventHandler(this.RaiseAlert); 18: objWatcher.Created += new FileSystemEventHandler(this.RaiseAlert); 19: objWatcher.Deleted += new FileSystemEventHandler(this.RaiseAlert); 20: 21: this.Text = "Listing 11.7"; 22: } 23: 24: private void RaiseAlert(Object Sender, FileSystemEventArgs e) {
403
Arbeiten mit der Windows-Ein-/Ausgabe
25: MessageBox.Show("Ich sehe dich!! Du " + e.ChangeType.ToString() + " " + e.Name); 26: } 27: 28: public static void Main() { 29: Application.Run(new Listing117()); 30: } 31: } 32: }
Die Zeilen 11 bis 15 richten den FileSystemWatcher ein. In diesem Fall überwacht er die Eigenschaften LastWrite, CreationTime und Size auf Veränderungen. In den Zeilen 17 bis 19 richten Sie die passenden Ereignisse ein, die ausgelöst werden sollen, wenn sich eine der erwähnten Eigenschaften ändert. Beachten Sie, dass diese Ereignisse jeweils einen Delegaten vom Typ FileSystemWatcherEventHandler verwenden, so dass Sie im Ereignishandler einen Ereignisparameter des Typs FileSystemEventArgs verwenden. Zeile 24 enthält diesen Ereignishandler. Er zeigt dem Benutzer eine Meldung an, wenn eine Änderung entdeckt wurde, wobei er eine Reihe von Eigenschaften des Objekts FileSystemEventArgs nutzt. ChangeType gibt die Art der erfolgten Änderung an. Folgende Werte der Aufzählung WatcherChangeTypes sind gültig: All (welcher alle Änderungsarten umfasst), Changed, Created, Deleted und Renamed. Die Name-Eigenschaft gibt den Verzeichnis- bzw. Dateinamen an, der geändert wurde (alternativ könnten Sie auch FullName verwenden, um den kompletten Verzeichnispfad zu liefern). Die MessageBox-Methode in Zeile 25 liefert eine generische Meldung an den Benutzer. Kompilieren Sie diese Anwendung, führen Sie sie aus und ändern Sie die Datei happy.txt (bzw. die entsprechende von Ihnen erstellte Datei). Die Anwendung sollte Sie dann alarmieren, wie etwa in Abbildung 11.7 zu sehen. Wenn Sie diese Datei bearbeiten, erhalten Sie von der Anwendung zwei Meldungen, die beide Changed lauten, da Sie ja zwei Eigenschaften der Datei änderten: Size und LastWrite. Denken Sie daran, Ihre NotifyFilter-Typen mit den Ereignissen, die ausgelöst werden, zu korrelieren. So nützt es Ihnen etwa nichts, wenn Sie die NotifyFilter-Eigenschaft auf NotifyFilters.FileName setzen, wenn Sie nicht das Renamed-Ereignis überwachen.
404
Asynchrone Ein-/Ausgabe
Abbildung 11.7: Ertappt!
11.4 Asynchrone Ein-/Ausgabe Standardmäßig waren alle E/A-Operationen, die Sie bislang vorgenommen haben, synchron. Das bedeutet, dass Ihre Anwendung nichts anderes tun kann, bis die fragliche E/A-Operation beendet wurde. Sie können also dem Benutzer keine Meldung anzeigen, während eine Datei gelesen oder geschrieben wird. Bis jetzt war das synchrone Verfahren kein Problem. Alle Arbeitsschritte wurden relativ rasch erledigt, so dass Sie sich nicht den Kopf darüber zerbrechen mussten, dass die Anwendung darauf wartet, mit anderen Arbeitsschritten fortzufahren, während die jeweilige E/A-Operation zu Ende geführt wird. In manchen Fällen kann man jedoch nicht darauf warten, bis die Ein-/Ausgabe fertig ist. Das trifft zum Beispiel zu, wenn man den Inhalt einer sehr umfangreichen Datei einliest oder darauf wartet, dass ein sehr langsames Netzwerk einen Stream bereitstellt. Diese Abläufe können mitunter sehr lange dauern. Bei synchronen Abläufen bleibt Ihnen keine andere Wahl, als den Benutzer Ihrer Anwendung warten zu lassen, bis die jeweiligen Vorgänge abgeschlossen sind. Um diesen Konflikten entgegentreten zu können, gibt Ihnen .NET die Möglichkeit zu asynchronen Operationen. Damit erfolgen Ihre E/A-Abläufe abgetrennt vom Rest der Anwendungsausführung, so dass Ihre Anwendung nicht auf den Abschluss langwieriger Vorgänge warten muss. Stellen Sie sich vor, dass alle Arbeitsschritte, die in Ihrer Anwendung ablaufen müssen (Initialisierung, Ereignisverarbeitung, Erstellen von Steuerelementen usw.) sich in einer Warteschlange anstellen, um ausgeführt zu werden. Bei asynchroner Arbeitsweise treten die wichtigsten Prozesse aus der Warteschlange heraus, erledigen die ihnen zugeteilten Aufgaben und reihen sich dann wieder ein.
405
Arbeiten mit der Windows-Ein-/Ausgabe
Asynchrone Operationen sind ein komplexes Thema, das gewöhnlich erfahrenen Benutzern vorbehalten bleibt, so dass wir nicht im Detail darauf eingehen. Das Erstellen einer »asynchronen« Anwendung erfordert häufig eine radikale Abkehr von den Entwurfsmustern, die Sie gewöhnt sind, und wird in der Realität auch nicht sehr häufig eingesetzt. Momentan genügt es zu wissen, dass Streams zusätzlich zu ihren normalen Lese-/Schreibmethoden auch über asynchrone Versionen dieser Methoden verfügen.
11.5 Drucken unter .NET Das letzte E/A-Thema für heute ist das Drucken. Um etwas drucken zu können, muss man durch ein paar mitunter seltsame Schleifen springen. Doch der grundlegende Funktionsumfang besteht aus dem PrintDocument-Objekt und dem PrintPage-Ereignis. Anders als sein Name suggeriert, ist PrintDocument keineswegs ein zu druckendes Dokument. Vielmehr ist es so etwas wie der Verkehrspolizist, der steuert, wie ein Dokument gedruckt wird. Es existiert, um den Vorgang anzustoßen und für einen reibungslosen Ablauf zu sorgen. Sobald etwas zu drucken ist, löst das PrintDocument das PrintPage-Ereignis aus und überlässt es Ihnen, dieses Ereignis abzufangen und das Dokument tatsächlich zu drucken. Bevor Sie dies lernen, schauen wir uns die Grundlagen an. Angenommen, Sie wollen dem Benutzer gestatten, den Inhalt der RichTextBox in Listing 11.6 zu drucken. (In den folgenden Beispielen verwenden wir C#-Code, so dass Sie gegebenenfalls von VB .NET in C# bzw. umgekehrt konvertieren müssen, damit die angegebenen Codefragmente funktionieren.) Zuerst sorgen Sie dafür, dass die nötigen Namensräume importiert werden: using System.Drawing.Printing;
Dann fügen Sie ein Menüelement hinzu, so dass der Benutzer das Dokument drucken kann: MainMenu mnuMain = new MainMenu(); MenuItem miFile = new MenuItem("Datei "); MenuItem miPrint = new MenuItem("Drucken "); miPrint.Click += new EventHandler(this.PrintClicked); mnuMain.MenuItems.Add(miFile); miFile.MenuItems.Add(miPrint);
Im Ereignishandler PrintClicked können Sie das Dialogfeld PrintDialog anzeigen, wenn der Benutzer seine Druckeinstellungen festlegen können soll, bevor er tatsächlich druckt. (Informationen zum Konfigurieren der Druck- und Seiteneinstellungen finden Sie an Tag 7.) Im Moment überspringen wir diesen Schritt und drucken einfach das Dokument. Der Ereignishandler sollte wie im folgenden Codestück aussehen:
406
Drucken unter .NET
private void PrintClicked(Object Sender,EventArgs e){ PrintDocument pd = new PrintDocument(); pd.PrintPage += new PrintPageEventHandler(this.pd_PrintPage); pd.Print(); }
Beachten Sie diese drei Codezeilen: das Anlegen eines PrintDocuments, die Zuweisung des Delegaten zum PrintPage-Ereignis und den Aufruf des Print-Befehls. Sie müssen noch nicht einmal angeben, was zu drucken ist. Sobald Sie eine pd_PrintPage-Methode erstellt haben, können Sie diese Anwendung kompilieren und ausführen. Ihr Drucker wird reagieren, wenn Sie auf das DRUCKEN-Menü klicken. Doch momentan wird nichts außer einer leeren Seite ausgegeben – es gibt nichts zu drucken. Für jede zu druckende Seite wird das PrintPage-Ereignis ausgelöst, so dass Sie das Dokument letztendlich in diesem Ereignishandler drucken. Wir schauen uns einmal den Ereignishandler in Listing 11.8 an. Listing 11.8: Der PrintPage-Ereignishandler 1: private void pd_PrintPage(Object Sender, PrintPageEventArgs e) { 2: StringReader objReader = new StringReader(rtbText.Text); 3: 4: Font fntPrint = this.Font; 5: int count = 0; 6: float yPos = 0; 7: float lpp = e.MarginBounds.Height / fntPrint.GetHeight(e.Graphics); 8: float fltTopMargin = e.MarginBounds.Top; 9: float fltLeftMargin = e.MarginBounds.Left; 10: String strLine = null; 11: 12: while (count < lpp && ((strLine = objReader.ReadLine()) != null)) { 13: yPos = fltTopMargin + (count * fntPrint.GetHeight(e.Graphics)); 14: 15: e.Graphics.DrawString(strLine, fntPrint, Brushes.Black, fltLeftMargin, yPos, new StringFormat()); 16: 17: count++; 18: } 19: 20: if (strLine != null) { 21: e.HasMorePages = true; 22: } else { 23: e.HasMorePages = false; 24: } 25: }
407
Arbeiten mit der Windows-Ein-/Ausgabe
Beachten Sie, dass dieser Ereignishandler ein PrintPageEventArgs-Objekt erfordert. Es enthält die für das Drucken notwendigen Eigenschaften. Zeile 2 holt den Text aus der früher erstellten RichTextBox. Sie müssen die zu druckende Information in einen Stream bringen oder in einen Reader, weil dies die einzigen Dinge sind, die .NET drucken kann. Darauf folgt einiges an Initialisierung. Sie müssen einige Daten über die zu bedruckende Seite kennen, bevor es losgehen kann. Sie müssen wissen, wo der Druckvorgang beginnen kann, wo die Ränder liegen, wie viel auf eine einzelne Seite passt, wie hoch die Druckqualität sein soll usw. Die Druckfunktionen von .NET sind alle im Namensraum System.Drawing abgelegt. Doch im Gegensatz zum Malen (Drawing) müssen Sie beim Drucken stets in der ersten Zeile anfangen (selbst wenn es nichts zu drucken gibt) und dann zeilenweise weitermachen. Wenn Sie mit der Arbeitsweise eines Druckers vertraut sind, wird Ihnen dieser Ablauf einleuchten. Der Druckkopf bewegt sich horizontal über eine Seite, druckt Zeile für Zeile und kehrt dann an den Anfang zurück, um die nächste Zeile zu drucken, ähnlich wie eine Schreibmaschine. Deshalb müssen Sie den Druckvorgang in der gleichen Weise angehen. Zeile 4 ermittelt die Schriftart, mit der zu drucken ist; in diesem Fall verwenden Sie die gleiche Schriftart, die die RichTextBox verwendet, so dass das gedruckte Dokument genauso aussehen wird wie das Dokument am Bildschirm. Zeile 5 initialisiert eine Variable, die Sie verwenden, um sich per Schleife durch die Zeilen des Dokuments zu bewegen (gleich mehr dazu). Zeile 6 verrät uns, wo Sie zu drucken anfangen wollen: die y-Koordinate. Dieser Wert ist anfangs null und berücksichtigt nicht die Höhe der Ränder (gleich mehr zu den Rändern). Mit dem Ereignishandler in Listing 11.8 lässt sich nur eine Seite bedrucken. Ist Ihr Dokument länger, dann wird der Ereignishandler erneut ausgeführt und holt das Dokument noch einmal. Der Rest folgt automatisch und Ihre Anwendung tritt in eine endlose Druck-Schleife ein. Daher empfiehlt es sich, den Code in Zeile 2 außerhalb der PrintPage-Ereignishandlermethode unterzubringen. Zeile 7 bestimmt die Anzahl der Zeilen pro Seite. Diese Zahl wird bestimmt, indem man die Seitenhöhe durch die Höhe der verwendeten Schrift dividiert. Die Angabe der Seitenhöhe wird vom PrintPageEventArgs-Objekt geliefert. Denken Sie daran, die Höhe der Ränder in Ihrer Berechnung zu berücksichtigen, was der Grund dafür ist, dass Zeile 7 die Höhe der MarginBounds-Eigenschaft bestimmt. Das Font-Objekt verfügt über eine GetHeight-Methode, welche die Höhe der aktuell verwendeten Schrift bestimmt. Um den Parameter für GetHeight brauchen Sie sich noch nicht zu kümmern.
408
Drucken unter .NET
Die Zeilen 8 und 9 bestimmen jeweils die linken und oberen Ränder. Zeile 10 schließlich initialisiert eine Variable, die Sie gleich verwenden werden. Denken Sie daran, dass das Drucken zeilenweise erfolgt. Daher müssen Sie eine Schleife einrichten, die durch die Zeilen im von Ihnen eingerichteten Reader iteriert. Dies tut die while-Schleife (Zeile 12). Wenn sie das Seitenende erreicht (welches durch die Angabe der Zeilen pro Seite definiert ist) oder wenn es keinen Inhalt mehr zu drucken gibt, endet die Schleife. Beachten Sie die Shortcut-Syntax in Zeile 12; Sie können direkt im whileStatement einer Variablen einen Wert zuweisen. Sie verwenden ReadLine im Reader, um die aktuell zu druckende Zeile zu liefern. Die Zeile 13 bestimmt, wo genau die aktuelle Zeile auf der Seite gedruckt werden soll. Sie addieren die Randbreite und rechnen dann aus, wie viele Zeilen Sie bereits gedruckt haben, indem Sie Ihren Zeilenzuwachs mit der Schrifthöhe multiplizieren. Zum Schluss sind Sie endlich bereit, die Seite zu drucken (Zeile 15). .NET behandelt die zu druckende Seite wie eine Leinwand. Um eine Zeile zu drucken, rufen Sie die Methode DrawString des Graphics-Objekts auf, das mit Ihrem Drucker verknüpft ist. (Diese Begriffe ergeben ab Tag 13 etwas mehr Sinn). DrawString übernimmt sechs Parameter in der folgenden Reihenfolge: 쐽
String: Die auf der Seite zu zeichnende Zeichenfolge.
쐽
Font: Die Schriftart für das Drucken der Zeichenfolge.
쐽
Brush: Ein Objekt, das dem Drucker mitteilt, wie die Zeichenfolge zu drucken ist (in welcher Farbe, ob ausgefüllt oder mit Verlauf usw.) Mehr zu diesem Objekt erfahren Sie an Tag 13.
쐽
X: Die x-Koordinate für den Druckbeginn.
쐽
Y: Die y-Koordinate für den Druckbeginn.
쐽
StringFormat: Ein Objekt, das die zu druckende Zeichenfolge formatiert (vertikale
oder horizontale Ausrichtung bzw. Hoch- oder Querformat etc.). Sie kennen bereits die meisten dieser Parameter, so etwa String, X und Y. Für Brush und StringFormat können Sie generische Werte benutzen, wie in Zeile 15 gezeigt. In Zeile 17 inkrementieren Sie den Zähler, der Ihnen verrät, welche Zeile gerade gedruckt wird. Tabelle 11.4 führt Sie durch die komplette while-Schleife in den Zeilen 12 bis 18.
409
Arbeiten mit der Windows-Ein-/Ausgabe
Ausgabedatei, Zeilennummer
x- und y-Koordinaten der Zeile
Zeileninhalte (aus der weiter vorn in dieser Lektion erzeugten Datei happy.txt)
1
0, oberer Rand
Hallo Welt!
2
0, oberer Rand + Höhe von Zeile 1 Hier ist noch mehr
3
0, oberer Rand + Höhe von Zeile 1 Text, den ich hier eingefügt habe + Höhe von Zeile 2
4
0, oberer Rand + Höhe der Zeilen 1, 2, 3
um die Applikation
5
0, oberer Rand + Höhe der Zeilen 1, 2, 3, 4
in Listing 11.3 zu testen.
6
0, oberer Rand + Höhe der Zeilen 1, 2, 3, 4, 5
Null
Tabelle 11.4: Der Druckvorgang
Wir wollen uns den Rest des Codes in Listing 11.8 ansehen: 20: 21: 22: 23: 24:
if (strLine != null){ e.HasMorePages = true; } else { e.HasMorePages = false; }
Das Objekt PrintPageEventArgs hat eine HasMorePages-Eigenschaft, die angibt, ob es noch mehr Seiten zu drucken gibt. Ist dies der Fall, wird ein weiteres PrintPage-Ereignis ausgelöst und der gesamte Druckvorgang beginnt von neuem. Leider weiß die Anwendung selbst nicht, ob es mehr Seiten zu drucken gibt; das müssen Sie selbst ausrechnen. Daher bestimmen Sie in Zeile 20, ob die letzte Zeichenfolge, die aus der RichTextBox gelesen wurde, null ist. Ist das der Fall, heißt das, dass es keine Zeilen mehr zu drucken gibt und man fertig ist. An diesem Punkt setzen Sie HasMorePages auf false. Wenn Sie aber mehr Text auf einer weiteren Seite zu drucken haben, dann lösen Sie wieder PrintPage aus. In diesem Fall setzen Sie HasMorePages auf true (Zeile 21). Beachten Sie, dass dieser Codeabschnitt nach der while-Schleife folgt, so dass die aktuelle Seite bereits vollständig gedruckt worden ist (gemäß dem Zeilen/Seite-Wert bzw. dann, wenn der Inhalt der RichTextBox komplett gedruckt ist). Dieser Ablauf ist recht kompliziert, daher wollen wir ihn zusammenfassen: 1. Erstellen Sie eine Möglichkeit, wie der Benutzer das Dokument drucken kann (also das Menüelement).
410
Zusammenfassung
2. Erzeugen Sie das PrintDocument-Objekt, weisen Sie dem PrintPage-Ereignis einen Ereignishandler zu und rufen Sie PrintDocument.Print auf. 3. Bestimmen Sie im PrintPage-Ereignishandler die Eigenschaften der Seite, darunter die Zeilen/Seite, die Randbreite und die zu verwendende Schriftart. Erstellen Sie ein Stream- oder Readerobjekt, das den zu druckenden Inhalt enthält. 4. Durchlaufen Sie in einer Schleife die Zeilen des Streams oder Readers, bis Sie den Zeilen/Seite-Wert oder das Ende des Readers erreicht haben. Legen Sie die y-Koordinate für den Druckbeginn in jeder Zeile fest. Drucken Sie die Zeile, indem Sie Graphics.DrawString im PrintPageEventArgs-Objekt aufrufen und ihm die notwendigen Parameter übergeben. 5. Bestimmen Sie, ob Sie noch mehr Inhalt zu drucken haben, und legen Sie HasMorePages entsprechend fest. Wenn Sie dem Benutzer erlaubt haben, die Seiteneinstellungen zu ändern, können Sie die geänderten Werte dem PrintDocument zuweisen, wie im folgenden Beispiel: PageSettings objSettings = new PageSettings(); PrinterSettings objPrinter = new PrinterSettings(); //fügen Sie hier Code ein, um die Einstellungen anzupassen //(möglichweise mit Standarddialogfeldern) PrintDocument pd = new PrintDocument(); pd.DefaultPageSettings = objSettings; pd.PrinterSettings = objPrinter;
Sobald das PrintPage-Ereignis ausgelöst wird, enthält das PrintPageEventArgs-Objekt die neuen Einstellungen (wie etwa Randänderungen usw.). Sie können einen Druckauftrag auch löschen, indem Sie die Cancel-Eigenschaft des PrintPageEventArgs-Objekts auf true setzen.
11.6 Zusammenfassung Sie haben nun von Streams und ihrer Rolle bei E/A erfahren. Jedes Mal, wenn Sie es mit E/A unter .NET zu tun haben, gehen Sie mit Streams um, die quellenunabhängige Informationsspeicher darstellen, denen Sie Writer und Reader zuweisen können, um deren Inhalte zu bearbeiten. Obwohl es zahlreiche verschiedene Arten von Streams, Readern und Writern gibt, ist ihre Arbeitsweise doch stets ähnlich. Um eine Datei zu öffnen, erstellen Sie für sie einfach einen Stream mit den erforderlichen FileMode-, FileAccess- und FileShare-Aufzählungswerten. Die Anwendung kann alle
Funktionen handhaben, um festzustellen, ob die Datei existiert, ob sie überschrieben oder
411
Arbeiten mit der Windows-Ein-/Ausgabe
erstellt werden soll usw. Als Nächstes weisen Sie einen Reader oder Writer des passenden Typs zu (StreamReader und StreamWriter für die meisten Textdateien) und rufen eine der folgenden E/A-Methoden auf: Write, WriteLine, ReadLine, Read, ReadBlock, ReadToEnd usw. Das RichTextBox-Objekt verfügt über eingebaute E/A-Funktionen. Sie müssen lediglich die LoadFile- und SaveFile-Methoden aufrufen, um ein vorhandenes Dokument bearbeiten zu können. Das FileSystemWatcher-Objekt erweist sich als nützlich, wenn es darum geht, die vor sich gehenden Änderungen in Ihrem Dateisystem zu überwachen. Sie können sich jedes Mal, wenn eine Datei erstellt, geändert, umbenannt oder sonst wie verändert wird, benachrichtigen lassen und dann entsprechend darauf reagieren. Dieses Objekt sollte man nicht für Sicherheitszwecke missbrauchen; dafür gibt es viel besser geeignete Objekte. Zum Schluss haben Sie das Drucken von Inhalten erlernt. Drucken ist keineswegs ein intuitiv verständlicher Prozess. Sie erstellen ein PrintDocument-Objekt und rufen eine Print-Methode auf, die wiederum Ereignisse auslöst, in welche Sie eine Schleife einflechten müssen, die durch den zu druckenden Inhalt führt. Schließlich »zeichnen« Sie den Inhalt auf das Papier. Nachdem Sie das gelernt haben, können Sie den gleichen Code zum Glück fast überall einsetzen, denn die Drucklogik ändert sich recht selten.
11.7 Fragen und Antworten F
Kann ich Streams, Writer und Reader oder auch eine RichTextBox dazu verwenden, Word-Dokumente zu bearbeiten? A
Nicht ganz. Word-Dokumente sind kein reiner Text, sondern verwenden vielmehr ein Binärformat, das exklusiv von Microsoft entwickelt wurde. Dieses Format erlaubt Microsoft nicht nur, eigene Funktionen in Word-Dokumente einzubauen, sondern stellt auch sicher, dass nur Benutzer, die Word (oder die Lizenz dafür) besitzen, Word-Dokumente bearbeiten können. Sie können binäre Reader und Writer dazu einsetzen, Word-Dokumente zu bearbeiten. Doch alle Dateien weisen ein spezifisch geordnetes Format auf. Wenn Sie also nicht ganz genau wissen, wie ein Word-Dokument intern aufgebaut ist, dürfte Ihre Mühe umsonst sein. Microsoft gibt die Informationen über das binäre Dateiformat an Unternehmen weiter, die Word lizenzieren möchten. Wenn Sie zu einer solchen Firma gehören, dann viel Erfolg!
F
Ich habe von »isolierter Speicherung« in .NET gehört. Was versteht man darunter? A
412
Isolierte Speicherung ist ein interessantes Konzept. Damit ist gemeint, dass jeder Benutzer Ihrer Anwendung über sein eigenes dediziertes Repository (Speichersystem) für solche Dokumente verfügt, die während der Anwendungsausführung
Workshop
bearbeitet werden. Dieses Repository ist sicher und vom Betriebssystem beschützt, so dass sich vertrauliche Daten an diesem Speicherplatz ablegen lassen. Sie können zudem Kontingente für die dort speicherbare Datenmenge einrichten. Isolierte Speicherung wird nur selten in Desktop-Anwendungen eingesetzt. Sie ist gebräuchlicher in Internet-Applikationen, bei denen viele Benutzer auf einen Rechner zugreifen.
11.8 Workshop Dieser Workshop soll Ihnen helfen, sich besser an die heute behandelten Begriffe zu erinnern. Es hilft Ihnen sehr, die Antworten vollständig zu verstehen, bevor Sie weiterlesen. Die Antworten zu den Quizfragen und den Übungen finden Sie in Anhang A.
Quiz 1. Worin besteht der Unterschied zwischen den Read- und Peek-Methoden? 2. Was ist eine rekursive Funktion und wann erweist sie sich als hilfreich? 3. Worin besteht der Unterschied zwischen synchroner und asynchroner Arbeitsweise? 4. Zu welchem Namensraum gehört die Druckfunktionalität? 5. Welche sind die vier Ereignisse des FileSystemWatcher-Objekts? 6. Welche sind die Werte der FileMode-Aufzählung ?
Übung Modifizieren Sie Listing 11.1 so, dass die TreeView-Knoten jedes Mal dynamisch erstellt werden, wenn ein Benutzer einen Knoten expandiert, anstatt alle während der Initialisierungsphase der Anwendung erstellen zu lassen. Fügen Sie außerdem eine benutzerdefinierte Druckfunktion hinzu, um die sichtbaren Inhalte des TreeView-Steuerelements drucken zu können. (Tipp: Sie müssen die Inhalte der Strukturansicht erst in einer passenden Variablen sammeln, um sie drucken zu können.)
413
Formulare Internetfähig machen
2 1
Formulare Internet-fähig machen
Das Internet ist mittlerweile ein zentraler Aspekt der Computerverwendung. Daher wird es nur den Tatsachen gerecht, wenn Sie das Internet entsprechend in Ihren Anwendungen berücksichtigen. Dank .NET ist die Integration von Internetfunktionen ein Kinderspiel. Heute werfen Sie einen Blick auf verschiedene Internetprotokolle und darauf, wie man sie unter .NET einsetzt. Sie werden erkennen, dass Sie damit verschiedene Aufgaben leicht erledigen können, von einfachen Webanfragen bis zum Zugriff auf sichere Webserver. In kürzester Zeit werden Sie webfähige Anwendungen auf die Beine stellen können. Heute lernen Sie, 쐽
was Internet- und austauschbare Protokolle sind,
쐽
wie das WebRequest-Objekt die Arbeit erleichtert,
쐽
was ASP.NET ist,
쐽
auf welche Weise .NET mit dem Internet Explorer integriert ist,
쐽
wie man auf sichere Websites zugreift.
12.1 Internetprotokolle Wenn Sie schon einmal das Internet benutzt haben, dann haben Sie wohl auch bereits von den verschiedenen Protokollen gehört, darunter IP, FTP, HTTP, UDP und TCP. Sie werden alle eingesetzt, damit das Internet funktioniert. Das bekannteste Protokoll ist das Hypertext Transfer Protocol (HTTP). Es wird am häufigsten in Browsern wie dem Internet Explorer oder dem Netscape Communicator eingesetzt. Man benutzt es, um Hypertext Markup Language- (HTML-) Seiten zur Anzeige auf den Browser zu holen. Ohne HTTP wäre das Internet wahrscheinlich nicht das, was Sie heute kennen. Das File Transfer Protocol (FTP) ist ebenfalls recht gebräuchlich. FTP wird hauptsächlich für die Dateiübertragung zwischen Computern eingesetzt. Es wurde so entworfen, dass es für die Übertragung großer Dateien effizienter ist als HTTP. Es gab einmal eine Zeit, zu der man alle Tricks dieser Protokolle kennen musste, um einer Anwendung Internetfähigkeiten hinzuzufügen. Jedes unterscheidet sich von den anderen, so dass sich das Problem vergrößert, wenn man mehrere zusammen einsetzen will. .NET vereinfacht diese Aufgabe für den Entwickler. Wir wollen uns zunächst noch ansehen, wie das Internet arbeitet. Ein Großteil der Internetanwendungen fußt auf dem Client/Server-Modell (auch unter der Bezeichnung Request/Response- bzw. Anforderung/Antwort-Modell bekannt). In diesem
416
Internetprotokolle
Paradigma dient ein Rechner, der so genannte Server, als Informationsspeicher und andere Rechner dienen als Verbraucher dieser Informationen und werden als Clients bezeichnet. Der Client schickt eine Anforderung (Request; via HTTP) an den Server und dieser antwortet (Response), indem er die angeforderten Daten zurück an den Client schickt (wiederum via HTTP; vgl. Abbildung 12.1). Konkret ausgedrückt: (1.) Sie besuchen eine Website, wo Sie als Client auftreten, (2.) Ihr Browser schickt eine HTTP-Anforderung an den Server und (3.) diese Website schickt die angeforderte Seite. Das Gesamtkonzept ist einfach, aber effizient in seiner Arbeitsweise. Beachten Sie, dass sowohl der Client als auch der Server beliebige Rechner sein können. SERVER BROWSER Anforderung Verarbeitung der Anforderung
Antwort
Abbildung 12.1: Im Client/Server-Modell speichert ein Computer (Server) Informationen und ein anderer (Client) ruft sie ab.
Wenn Millionen von Clients und Servern mit dem Internet verbunden sind, kann sich die Suche nach einem bestimmten Server recht schwierig gestalten. Daher wurde das System der Universal Resource Identifier (URI; manchmal wird auch URL verwendet, siehe den Hinweis unten) entwickelt; dieses System bildet den Schlüssel zu den Internetfunktionen in .NET. Sie haben sicher schon einmal URIs wie den folgenden gesehen: http://www.clpayne.com/default.asp?blah=bloh
Ein Uniform Resource Locator (URL), mit dem viele Menschen vertrauter sind, ist eine Form des URI, die bei Netzwerkprotokollen arbeitet, um bestimmte Adressen zu finden. Der URI besteht aus vier Teilen. Der erste, nämlich http://, gibt das Protokoll an, das verwendet werden soll, wenn Ressourcen vom Server angefordert werden. Dieser Teil wird auch Schemabezeichner (Schema Identifier) genannt. (Sie brauchen sich diesen Begriff nicht zu merken.) Der nächste Teil, www.clpayne.com, ist der Name des Servers (im Web). Das kann ein benutzerfreundlicher Name wie der hier gezeigte sein (dank des Domain Naming Systems oder DNS) oder einfach eine IP-Adresse wie etwa 127.0.0.1. Dieser Servername ist im Wesentlichen die Adresse des Servers, ähnlich wie Ihre Hausadresse. Der Unterschied: Dieser Servername soll im gesamten Internet nur ein einziges Mal (unique) vorkommen, also eindeutig sein (leider kommen Konflikte dennoch vor).
417
Formulare Internet-fähig machen
Der folgende Teil, nämlich /default.asp, ist der Pfadbezeichner (Path Identifier), der dem Server verrät, was genau der Client nun eigentlich anfordert. In diesem Fall ist es eine Active Server Page namens default.asp. Vielfach handelt es sich auch um eine HTMLSeite wie etwa index.html. Der vierte Teil ist eine optionale Abfragezeichenfolge. Diese Zeichenfolge, nämlich ?blah=bloh, wird dem Ende des Pfadbezeichners durch ein Fragezeichen angehängt und
gibt alle zusätzlichen Parameter an, die der Server für die Verarbeitung der Anfrage benötigen könnte. Wie Sie wahrscheinlich erwartet haben, sind alle Teile durch Klassen im .NET Framework repräsentiert.
Austauschbare Protokolle Die wichtigsten Internetklassen für Windows Forms-Anwendungen unter .NET sind WebRequest und WebResponse. WebRequest schickt Anforderungen mit einem URI an einen Server und WebResponse sendet Informationen zurück an den anfragenden Client. Das Interessante an diesen Klassen (und anderen .NET-Internetklassen) ist, dass sie mit jedem Protokoll zusammenarbeiten, so dass Sie Ihre Anwendung nicht ändern oder neuen Code schreiben müssen, nur um ein anderes oder neues Protokoll verwenden zu können. WebRequest und WebResponse dienen als eine Art Verkehrspolizisten, ähnlich der SqlDataAdapter-Klasse von Tag 9. Bitte erinnern Sie sich, dass SqlDataAdapter verschiedene SqlCommand-Objekte erzeugte, je nachdem, welche Art von SQL-Abfrage ausgeführt wurde. Wenn Sie WebRequest verwenden, um eine Information vom Server anzufordern, registriert
das Objekt automatisch das Protokoll, das Sie verwenden wollen, und erzeugt das notwendige Helferobjekt, um die Anforderung zu verarbeiten (so etwa ein HttpWebRequest-Objekt für HTTP-Anforderungen). Weil diese Helferobjekte dynamisch von einem Verkehrsobjekt abgeschaltet werden können, sind sie als austauschbare Protokolle (Pluggable Protocols, wörtlich: »einsteckbare Protokolle«) bekannt. .NET steckt bei Bedarf die jeweils nötige Klasse ein. Auf diese Weise können Sie eine Anwendung vom BrowserTyp allein mit WebRequest aufbauen. Sie brauchen sich auch nicht darum zu kümmern, welche Adressart der Benutzer in Ihren Browser eingibt, denn WebRequest passt sich dem dynamisch an. Dies ist die Grundlage für Internetanwendungen unter .NET: Einfachheit und Leichtigkeit für den Entwickler, während für den Benutzer die Leistungsfähigkeit beibehalten wird.
418
Clients und Server
12.2 Clients und Server Wie bereits besprochen, dient ein Server als Repository für Informationen und ein Client als Benutzer dieser Information. Weil ein Großteil des Internets aus Servern und Clients besteht, werden Sie meist diese beiden Anwendungstypen entwickeln. In den nächsten Abschnitten betrachten wir, wie man Clients erstellt, die mit Servern zusammenspielen; Serveranwendungen sind häufig ASP.NET vorbehalten. Für einige der folgenden Beispiele brauchen Sie einen Webserver, der Ihre Anforderungen empfangen kann. In manchen Fällen ist ein öffentlich zugänglicher Server wie www.clpayne.com oder www.microsoft.com ausreichend, doch manchmal brauchen Sie einen, mit dem Sie experimentieren können. Es ist wahrscheinlich, dass auf Ihrem Computer bereits ein Webserver läuft, nämlich Internet Information Server (IIS) von Microsoft. Um dies herauszufinden, geben Sie in der Adresszeile des Browsers einfach http://localhost ein. Erscheint eine Fehlerseite, läuft noch kein Server, so dass Sie IIS zunächst installieren müssen. Sollte eine Begrüßungsseite erscheinen oder gar eine Webseite, dann sind Sie schon startbereit. Um den IIS einzurichten, benötigen Sie Ihre Windows-Installations-CD. IIS ist eine optionale Komponente, die Sie über die SYSTEMSTEUERUNG und die Seite SOFTWARE/HINZUFÜGEN/ENTFERNEN installieren können. Klicken Sie auf die Schaltfläche HINZUFÜGEN/ENTFERNEN, wählen Sie die Optionen für den IIS, klicken Sie auf WEITER und folgen Sie dann den Anweisungen, um mit dem IIS-Einsatz anfangen zu können.
Anforderungen mit WebRequest senden Das Senden einer Anforderung ist nicht schwer. In der einfachsten Form braucht man nur ein paar neue Zeilen Code, um diese Funktion zu verwenden. Wir beginnen mit Listing 12.1. Listing 12.1: Ihre erste Internet-Applikation 1: 2: 3: 4: 5: 6: 7:
using using using using using
System; System.Windows.Forms; System.Drawing; System.Net; System.IO;
namespace TYWinforms.Day12 {
419
Formulare Internet-fähig machen
8: public class Listing121 : Form { 9: private RichTextBox rtbWeb = new RichTextBox(); 10: 11: public Listing121() { 12: CallServer(); 13: 14: rtbWeb.Multiline = true; 15: rtbWeb.Dock = DockStyle.Fill; 16: 17: this.Text = "Listing 12.1"; 18: this.Size = new Size(800,600); 19: this.Controls.Add(rtbWeb); 20: } 21: 22: private void CallServer() { 23: WebRequest objRequest = WebRequest.Create("http:// www.clpayne.com"); 24: WebResponse objResponse = objRequest.GetResponse(); 25: 26: StreamReader objReader = new StreamReader(objResponse.GetResponseStream()); 27: 28: rtbWeb.Text = objReader.ReadToEnd(); 29: 30: objReader.Close(); 31: objResponse.Close(); 32: } 33: 34: public static void Main() { 35: Application.Run(new Listing121()); 36: } 37: } 38: }
Die ersten 20 Zeilen sind im Grunde Standard. In Zeile 4 achten Sie bitte auf die Verwendung des zusätzlichen Namensraums System.Net, der alle nötigen Internetklassen enthält. Die eigentliche Internetanforderung erfolgt in dem Aufruf der angepassten CallServer-Methode. Der erste Schritt zur Erstellung einer Internetverbindung ist die Erzeugung eines WebRequest-Objekts, wie in Zeile 23 gezeigt. Die einfachste Möglichkeit dazu besteht im Aufruf einer statischen Create-Methode, wobei man den URI der Internetressource übergibt, auf die man zugreifen möchte. (Dieser Wert ist auch über die RequestUri-Eigenschaft der WebRequest-Klasse verfügbar – ändern Sie einfach die URI-Zeichenfolge in die gewünschte Adresse und sorgen Sie
420
Clients und Server
dafür, dass Sie eine Verbindung zum Internet haben.) Beachten Sie, dass Sie noch keine Verbindung hergestellt, sondern nur die Vorbereitungen dafür getroffen haben. In Zeile 24 stellen Sie die Verbindung her. Mit dem eben erstellten WebRequestObjekt rufen Sie GetResponse auf und speichern das Ergebnis in einem WebResponse-Objekt. Als Nächstes holen Sie die Antwort des Servers aus dem WebResponse-Objekt, indem Sie GetResponseStream aufrufen, welcher ein Streamobjekt liefert. Zeile 26 ordnet diesem Stream einen Reader zu, so dass Sie auf die Inhalte zugreifen können. In Zeile 28 lesen Sie den Inhalt des Streams mit Hilfe des Readers und legen den Inhalt im Steuerelement RichTextBox ab. Vergessen Sie nicht, sowohl den Reader als auch das WebRequest-Objekt zu schließen. Abbildung 12.2 zeigt die Anwendung, nachdem sie die Webanforderung verarbeitet hat.
Abbildung 12.2: Nach der Ausführung einer Webanforderung erhalten Sie diese Ergebnisse.
Was haben wir vorliegen? Abbildung 12.2 zeigt den Inhalt, der von der Website www.clpayne.com geholt wurde. Das ist keine bunte Webseite, sondern vielmehr ihr HTML-Code. Sie sehen den HTML-Inhalt, weil jede Anwendung dies sieht, wenn sie eine Anforderung an einen Server abschickt. Der eigentliche Darstellungsteil ist eine separate Funktion.
421
Formulare Internet-fähig machen
Lassen Sie uns die beteiligten Schritte rekapitulieren: 1. Erstellen Sie ein WebRequest-Objekt mit WebRequest.Create. 2. Rufen Sie GetResponse auf, um die Verbindung zu öffnen. 3. Rufen Sie GetResponseStream auf, um den Inhalt zu holen, wobei Sie bei Bedarf einen Reader anhängen. 4. Schließen Sie die Webobjekte (und gegebenenfalls den Reader). Web-basierte Interaktionen stützen sich ebenso wie E/A-Operationen auf Streams. Sie eignen sich aus mehreren Gründen hervorragend fürs Web. Bei Verwendung eines Streams brauchen Sie nicht die gesamte Ressource herunterzuladen, bevor Sie etwas damit tun können. Sie können den Stream lesen, während er noch hereinkommt (ähnlich wie das Betrachten eines Streaming-Videos, bevor es ganz heruntergeladen ist). Bedenken Sie auch, dass Streams auf jegliche Art von Datenquelle zugreifen können, von HTML bis XML. Das erleichtert das Abrufen von Webinhalten erheblich. Mit Hilfe von WebRequest können Sie dem Webserver auch Nachrichten in Form eines Streams schicken. Dieses Posting ist ein Vorgang, bei dem der Client zusätzliche eigene Informationen schicken kann, die sich normalerweise nicht in der Anforderungsnachricht befinden. Diese Information kann auch Postings von Formularen umfassen. Wenn Sie ein Online-Formular ausfüllen und auf die Schaltfläche OK klicken, schicken Sie eigentlich eine Webanforderung an den Server, so dass dieser Ihre Daten verarbeiten kann. (Dementsprechend wird der zuvor benutzte Vorgang des Abholens als Get-Anforderung (GetRequest) bezeichnet.) Um diese Anwendung zu testen, wollen wir zunächst eine Webseite erstellen, die Formular-Postings akzeptiert. Listing 12.2 zeigt eine Beispielseite. Listing 12.2: formpost.aspx 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15:
422
<script runat="server"> public void Page_Load(Object if (Request.Form.Count == lblMessage.Text = "You } else { lblMessage.Text = "You } }
Sender, EventArgs e) { 0) { didn’t post anything!"; posted: " + Request.Form.ToString();
Clients und Server
Speichern Sie diese Datei unter dem Namen formpost.aspx in Ihrem StammWebverzeichnis (üblicherweise c:\inetpub\wwwroot, wenn Sie die Standardeinstellungen des IIS übernommen haben). Die Datei ist eine einfache ASP.NETSeite, doch wie Sie sehen, ähnelt sie einer Windows Forms-Datei. Damit die Seite formpost.aspx funktioniert, müssen Sie das ASP.NET-Modul installiert haben. Das Modul ist eine optionale Komponente des .NET Frameworks. Wir wollen Listing 12.2 kurz analysieren. In ASP.NET haben Sie es mit zwei Arten von Code zu tun: mit Server- und mit HTML-Code. Der HTML-Abschnitt obigen Listings ist in den Zeilen 13 bis 15 zu finden. Der Servercode sieht wie Code aus VB .NET oder C# aus und ist in den <script>-Tags enthalten, wie es die Zeilen 3 bis 11 zeigen. Dieser Code wird kompiliert und funktioniert fast genau wie Windows Forms-Anwendungen. ASP.NET-Seiten können eine Methode namens Page_Load aufweisen, die beim Aufruf der Seite durch den Browser ausgeführt wird, ähnlich wie ein Konstruktor für ein Windows Forms-Programm, wenn die Anwendung startet. Die Page_Load-Methode übernimmt die gleiche Parameterliste wie viele Windows Forms-Ereignisse, weil sie ein Ereignishandler für das Page_Load-Ereignis in ASP.NET ist, das beim Laden der Seite ausgelöst wird. ASP.NET verfügt über das Request-Objekt, das ähnlich wie das bekannte WebRequestObjekt funktioniert. Die Form-Eigenschaft ist eine Auflistung aller Werte, die dem Server gepostet wurden; hätten Sie ein Formular online ausgefüllt, fänden sich Ihre Informationen hier wieder. Wurde nichts gesendet, ist die Auflistung leer (die Count-Eigenschaft ist 0), und Sie können eine Meldung (also die »schlechte Nachricht«) im Label-Steuerelement auf Zeile 14 anzeigen. Das Label-Steuerelement entspricht einem Windows FormsLabel, so dass dieser Code vertraut aussehen dürfte. Befindet sich jedoch etwas im Posting des Formulars, zeigen Sie eine »gute Nachricht« an, die aus »You posted: « und dem gesendeten Wert besteht (Zeile 8). Request.Form.ToString schreibt lediglich alles Gesendete in Klartext. Testen Sie die Seite, indem Sie in Ihrem Browser auf die Adresse http://localhost/formpost.aspx gehen. Sie sollten die Meldung »You didn't post anything!« erhalten, da Sie ja noch keine Daten gesendet haben. Wir kommen gleich dazu. Dieser Abschnitt hat einen kurzen Überblick über ASP.NET geboten, doch wie Sie sehen können, ist dieses Thema nicht sonderlich kompliziert, sobald Sie einmal Windows Forms kennen gelernt haben. Wir wollen nun die Windows Forms-Anwendung erstellen, die dieses Formular postet. Listing 12.3 zeigt den entsprechenden Code.
423
Formulare Internet-fähig machen
Listing 12.3: Daten an Ihre ASP.NET-Seite senden 1: using System; 2: using System.Windows.Forms; 3: using System.Drawing; 4: using System.Net; 5: using System.IO; 6: 7: namespace TYWinforms.Day12 { 8: public class Listing123 : Form { 9: private RichTextBox rtbWeb = new RichTextBox(); 10: private WebRequest objRequest; 11: public Listing123() { 12: rtbWeb.Dock = DockStyle.Fill; 13: 14: objRequest = WebRequest.Create("http://localhost/formpost.aspx"); 15: 16: PostData(); 17: GetData(); 18: 19: this.Text = "Listing 12.3"; 20: this.Controls.Add(rtbWeb); 21: } 22: 23: private void PostData() { 24: objRequest.Method = "POST"; 25: objRequest.ContentLength = 23; 26: objRequest.ContentType = "application/x-www-form-urlencoded"; 27: 28: StreamWriter objWriter = new StreamWriter (objRequest.GetRequestStream()); 29: objWriter.Write("Hi this is a form post!"); 30: objWriter.Close(); 31: } 32: 33: private void GetData() { 34: WebResponse objResponse = objRequest.GetResponse(); 35: StreamReader objReader = new StreamReader (objResponse.GetResponseStream()); 36: 37: rtbWeb.Text = objReader.ReadToEnd(); 38: 39: objReader.Close(); 40: objResponse.Close(); 41: } 42:
424
Clients und Server
43: 44: 45: 46: 47:
public static void Main() { Application.Run(new Listing123()); } } }
Die Anwendung in Listing 12.3 sendet einige Daten an die Datei formpost.aspx (Listing 12.2) und zeigt die Ergebnisse in einem in Zeile 9 deklarierten RichTextBox-Steuerelement an. Der Konstruktor ist recht einfach; er ruft eine PostData-Methode auf, um die Daten an den Server zu senden, und GetData, um die Antwort zu empfangen. Beachten Sie die Erstellung des WebRequest-Objekts in Zeile 14; es zeigt auf die eben erzeugte Datei formpost.aspx. Wir wollen einen Blick auf PostData in Zeile 23 werfen. Um dem Server Daten zu senden, müssen Sie ihm erst dieses Vorhaben mitteilen, indem Sie die Method-Eigenschaft des WebRequest-Objekts in Zeile 24 auf POST setzen. Nachdem der Server diese Information erhalten hat, weiß er, dass er auf Daten vom Client warten soll. In Zeile 25 legen Sie die Länge des zu sendenden Inhalts fest; in diesem Fall ist es die Länge einer Zeichenfolge. Die Längenangabe ist optional, im Allgemeinen empfiehlt es sich aber, diesen Wert bereitzustellen. In Zeile 26 setzen Sie die ContentType-Eigenschaft des WebRequest-Objekts, was sehr wichtig ist. Der Standardwert lautet auf "text/html", so dass reiner Text, als HTML formatiert, übertragen wird. Gepostete Daten jedoch werden nicht als reiner Text, sondern in einem speziell verschlüsselten Format übertragen. In Zeile 26 wird dieses Format als application/x-www-form-urlencoded festgelegt. Legen Sie dieses Format nicht fest, wird der Server die gesendeten Daten nicht erkennen und sie ignorieren. Nach dem Präparieren des Servers ist es Zeit für den Datenversand. Da alle E/AOperationen in .NET über Streams erfolgt, bilden Postings keine Ausnahme. In Zeile 28 rufen Sie daher die GetRequestStream-Funktion auf, um einen schreibbaren Stream zurückzugeben, der an den Server gesendet wird, und hüllen ihn dann in einen Writer ein. In Zeile 29 rufen Sie die Write-Methode auf (wie für jedes Writer-Objekt), um Ihre Daten schließlich an den Server zu schicken. Beachten Sie, dass die ContentLength-Eigenschaft in Zeile 25 der Länge des Zeichenfolge in Zeile 29 entspricht. Statt die Anzahl der Zeichen manuell zu zählen, können Sie folgenden Code benutzen: objRequest.ContentLength = "Hi, this is a form post!".Length;
Alternativ können Sie die zu sendenden Daten in einer Zeichenfolgenvariablen speichern und ihre Length-Eigenschaft aufrufen.
425
Formulare Internet-fähig machen
Abschließend müssen Sie genau wie beim Lesen von Anforderungsinformationen Ihren Writer schließen (siehe Zeile 30). Die GetData-Methode in den Zeilen 33 bis 41 ist praktisch identisch mit der CallServerMethode aus Listing 12.1. Sie rufen GetResponse auf, um ein WebResponse-Objekt zu erzeugen, rufen dann die Methode GetResponseStream auf und erstellen einen Reader; zum Schluss rufen Sie ReadToEnd auf. Sowohl der Reader als auch das WebResponse-Objekt werden dann beendet (in den Zeilen 39 und 40). Der einzige Unterschied zwischen diesen Methoden und dem früheren CallServer besteht darin, dass Sie hier gerade Daten an den Server gesendet haben, so dass Sie eine entsprechend zugeschnittene Antwort erwarten. Wenn Sie diese Anwendung kompilieren und ausführen, erhalten Sie Ergebnisse, die den in Abbildung 12.3 gezeigten ähneln.
Abbildung 12.3: Nach dem Posten an den Server erhalten Sie diese Ergebnisse.
In diesem Bild können Sie erkennen, dass der zurückgelieferte Inhalt aus HTML besteht. Das erwartete Ergebnis wird geliefert: You posted: Hi+this+is+a+form+post!
Weil die ASP.NET-Seite gepostete Formulardaten erhalten hat, zeigt sie die »gute Nachricht« mit den gesendeten Daten an. Beachten Sie jedoch das Pluszeichen zwischen jedem Wort. Da die gesendeten Daten in einem Spezialformat geschickt wurden, besteht einer der bei Postings notwendigen Formatierungstricks im Ersetzen von Leer- durch Pluszeichen. Das WebRequest-Objekt ist recht vielseitig, denn es erlaubt Ihnen, Inhalte von jedem Server zu holen bzw. an beliebige Server zu schicken. Vor der Einführung von .NET gestaltete sich das Holen und Posten von Daten weitaus schwieriger als nur das Lesen oder Schreiben eines Streams. Tabelle 12.1 zeigt die Eigenschaften und einige Methoden des WebRequest-Objekts.
426
Clients und Server
Eigenschaft
Beschreibung
ConnectionGroup- Diese Eigenschaft wird zum Gruppieren von Verbindungen (Connection Name Pools) benutzt. Diese werden zur Leistungssteigerung eingesetzt, wenn mehrere
Verbindungen nötig sind. ContentLength
Die Länge der übermittelten Daten.
ContentType
Typ und Format der übermittelten Daten.
Credentials
Anmeldeinformationen, die für den Zugang zu Webressourcen benutzt werden. (Mehr dazu im Abschnitt über Sicherheit.)
Headers
Die Auflistung von HTTP-Headern, die mit jeder Anforderung geschickt werden.
Method
Die Protokollmethode für diese Anforderung.
PreAuthenticate
Gibt an, ob dem Server Authentifizierungsinformationen gesendet werden sollen, bevor man dazu aufgefordert wird.
Proxy
Der für den Webzugang zu verwendende Proxy.
RequestUri
Der mit der Anforderung verknüpfte URI der Internetressource.
Timeout
Der Zeitraum, den die Anforderung auf eine Serverantwort maximal warten darf.
Methode
Beschreibung
GetRequestStream Liefert einen Stream, der sich für Datenlieferungen an den Server nutzen lässt. GetResponse
Liefert einen Stream, der die Serverantwort enthält.
Tabelle 12.1: Die Mitglieder von WebRequest
Anfragen mit WebResponse verarbeiten Die WebResponse-Klasse verarbeitet die Serverantwort. Diese einfache Klasse umfasst nicht viele Mitglieder. Tabelle 12.2 zeigt die Eigenschaften und Methoden von WebResponse. Eigenschaft
Beschreibung
ContentLength
Die Länge der übermittelten Daten.
ContentType
Typ und Format der übermittelten Daten.
Tabelle 12.2: Mitglieder von WebResponse
427
Formulare Internet-fähig machen
Eigenschaft
Beschreibung
Headers
Die Auflistung von HTTP-Headern, die mit einer Anforderung geschickt werden.
ResponseUri
Der URI der antwortenden Ressource.
Methode
Beschreibung
Close
Schließt den Antwortstream, so dass mithin auch die Verbindung zum Server beendet wird.
GetResponseStream
Liefert einen Stream, der die Antwort des Servers enthält.
Tabelle 12.2: Mitglieder von WebResponse (Forts.)
Protokollspezifische Klassen Bislang haben Sie gelernt, die Helferklassen WebRequest und WebResponse zu erstellen, wenn es um Web-Interaktionen geht. In den meisten Fällen genügen diese beiden Klassen. Die Helferklassen können aber auch zusätzliche Funktionen bereitstellen. Angenommen, Sie müssen einmal den Typ der Anwendung angeben, die auf den Server zugreift – wie zum Beispiel ein Internet Explorer-kompatibler Browser. Diese Informationen bezeichnet man als Benutzer-Agent (user-agent). Mit dem WebRequest-Objekt lässt sich der Typ nicht direkt bereitstellen, doch seine Helferklasse HttpWebRequest, die bei der HTTP-Verarbeitung hilft, kann dies: ((HttpWebRequest)objRequest).UserAgent = "Internet Explorer";
Um auf die HttpWebRequest-spezifischen Eigenschaften zuzugreifen, müssen Sie die WebRequest-Variable passend konvertieren, wie im obigen oder im folgenden Codefragment gezeigt: HttpWebRequest objHttpRequest = (HttpWebRequest)objRequest;
Tabelle 12.3 zeigt die Mitglieder von HttpWebRequest, zusätzlich zu denen in Tabelle 12.1. Eigenschaft
Beschreibung
Accept
Entspricht dem Accept-HTTP-Header.
Address
Gibt den URI des Servers an, der tatsächlich auf die Anforderung antwortet (kann sich von Uri unterscheiden).
Tabelle 12.3: Die Mitglieder von HttpWebRequest (ergänzend zu Tabelle 12.1)
428
Clients und Server
Eigenschaft
Beschreibung
AllowAutoRedirect
Gibt an, ob die Anforderung vom Server veranlasste Umleitungen zulassen soll.
AllowWriteStreamBuffering
Gibt an, ob der Antwortstream gepuffert werden soll (verbessert oftmals die Leistung).
ClientCertificates
Liefert Clientsicherheitszertifikate.
Connection
Entspricht dem Connection-HTTP-Header.
ContinueDelegate
Kennzeichnet einen Delegaten, der auf eine HTTP-100-Meldung antworten kann.
CookieContainer
Kennzeichnet die Cookies, die der Anforderung zugeordnet sind.
Expect
Entspricht dem Expect-HTTP-Header.
HaveResponse
Gibt an, ob vom Server eine Nachricht empfangen wurde.
IfModifiedSince
Entspricht dem If-Modified-Since-HTTP-Header.
KeepAlive
Gibt an, ob das Anforderungsobjekt eine dauerhafte Verbindung zum Server aufrechterhalten soll.
MaximumAutomaticRedirections
Legt die maximale Anzahl von Umleitungen fest, die das Anforderungsobjekt zulässt.
MediaType
Gibt den Medientyp der Anforderung an.
Pipelined
Gibt an, ob Anforderungen an den Server durch eine Pipeline erfolgen sollen (funktioniert nur, wenn KeepAlive auf true gesetzt wurde).
ProtocolVersion
Legt die zu verwendende HTTP-Version fest.
Referer
Entspricht dem Referer-HTTP-Header.
SendChunked
Gibt an, ob dem Server Daten in Segmenten geschickt werden sollen.
ServicePoint
Legt den Dienstpunkt fest, der für den Server zu verwenden ist.
TransferEncoding
Entspricht dem Transfer-encoding-HTTP-Header.
UserAgent
Entspricht dem User-agent-HTTP-Header.
Tabelle 12.3: Die Mitglieder von HttpWebRequest (ergänzend zu Tabelle 12.1) (Forts.)
Um mehr Informationen über die verschiedenen HTTP-Header zu erhalten, suchen Sie bitte die Website des WWW-Consortiums auf (www.w3c.org).
429
Formulare Internet-fähig machen
12.3 Arbeiten mit dem Internet Explorer Unter dem .NET Framework können Sie Ihre Windows Forms-Anwendungen mit dem Internet Explorer zusammenarbeiten lassen. Dies erspart Ihnen eine Menge der Zeit, die Sie früher mit dem Umschreiben Ihrer Desktop-Anwendungen für den Einsatz im Internet verwendet haben. Wenn Sie also zum Beispiel eine nützliche Windows Forms-Anwendung erstellt haben, dann kann jedermann sie mit Hilfe des Internet Explorers über das Internet erreichen (vorausgesetzt, man hat die korrekten Berechtigungen und zudem das .NET Framework installiert). Wir wollen die am 5. Tag erstellte Taschenrechner-Anwendung wiederverwenden. Verschieben Sie diese Anwendung in das Web-Stammverzeichnis Ihres Computers (c:\inetpub\wwwroot), öffnen Sie Ihren Browser und tippen Sie Folgendes in die Adresszeile ein: http://localhost/Anwendungsname.exe
(Ersetzen Sie Anwendungsname.exe durch den tatsächlichen Dateinamen der Anwendung.) Ihr Browser wird die Anwendung aufrufen, die nun genau wie eine normale Windows Forms-Anwendung läuft. Abbildung 12.4 zeigt das Ergebnis.
Windows Forms-Steuerelemente in den Internet Explorer einbetten Sie können in den Internet Explorer auch Windows Forms-Steuerelemente einbetten, wenn Sie das HTML-Tag verwenden. Listing 12.4 zeigt eine einfache HTMLSeite, die das TextBox-Steuerelement in den Browser einbettet. Listing 12.4: Windows Forms-Steuerelemente in Internet Explorer einbetten 1: 2:
3: 4: 5: 6: 7: 8:
<param name="Text" value="Hi there!"> <param name="Width" value="100"> <param name="Height" value="100"> <param name="Multiline" value="true">
Wenn Ihnen das -Tag nicht vertraut ist, so ist es leicht zu erlernen. Der erste Parameter ist id (in Zeile 2), der aus einem benutzerfreundlichen Namen besteht, den Sie verwenden können, um später in Ihrem Code auf die-
430
Arbeiten mit dem Internet Explorer
ses Steuerelement verweisen zu können. Im zweiten Parameter namens classid ist die Aktion spezifiziert. Um nun ein .NET-Steuerelement einzubetten, benötigen Sie zweierlei für den Parameter classid: den Namen und Pfad der Assembly oder ausführbaren Datei, die das Steuerelement enthält, das Sie einbetten wollen. Dann brauchen Sie (vom Pfadnamen durch ein Nummernzeichen # abgetrennt) den vollständigen Namensraum-Namen des fraglichen Steuerelements. Weil Sie das TextBox-Steuerelement einbetten wollen, setzen Sie den Assembly-Pfad auf System.Windows.Forms.dll und den Namensraum-Namen auf System.Windows.Forms.TextBox.
Abbildung 12.4: Sie können Ihre Windows FormsAnwendungen vom Browser aus ausführen.
Datei System.Windows.Forms.dll ist normalerweise im Ordner c:\winnt\Microsoft.NET\Framework\version abgelegt, doch für etwas mehr Komfort können Sie eine Kopie im Ordner C:\inetpub\wwwroot ablegen. Auf Die
diesen Ordner kann man über das Web zugreifen. Legen Sie diese Kopie nicht dort ab, kann es sein, dass Sie den Text in der Anwendung nicht sehen können. Als Nächstes verraten die Eigenschaften Height und Width (in den Zeilen 4 und 5) dem Browser, wie viel Bildschirmfläche das Steuerelement belegen darf. Dies betrifft nicht das tatsächliche Ergebnis, sondern nur, wie viel Fläche reserviert werden soll. VIEWASTEXT ist eine Helfereigenschaft, die dem Browser mitteilt, wie das Steuerelement dargestellt werden soll, falls es nicht kompatibel ist. Jede Eigenschaft des einzubettenden Steuerelements lässt sich mit Hilfe des PARAM-Tags offen legen, wie in den Zeilen 3 bis 6 zu sehen ist. Hier legen Sie die TextBox-Eigenschaf-
431
Formulare Internet-fähig machen
ten Text, Height, Width und Multiline offen und setzen Anfangswerte dafür. Beachten Sie, dass Sie das OBJECT-Tag auch wieder schließen müssen, wie in Zeile 7 zu sehen. Speichern Sie das Listing als HTML-Datei und betrachten Sie es in Ihrem Browser (obigen Tipp beachten). Sie sollten ein Ergebnis wie das in Abbildung 12.5 zu sehen bekommen.
Abbildung 12.5: Ihr TextBox-Steuerelement wurde in Internet Explorer eingebettet.
Wenn Sie wollen, können Sie die offen gelegten Eigenschaften per JavaScript ändern, so wie im folgenden Beispiel: function ChangeValues(){ MyControl.Text ="Ich habe soeben den Text geändert!"; MyControl.Width ="75 "; }
Sie verweisen auf das Steuerelement mit Hilfe des id-Wertes, den Sie oben in Listing 12.4 gesetzt haben. Natürlich wirkt dieses Beispiel etwas an den Haaren herbeigezogen. Statt ein solches Windows Forms-Steuerelement in Internet Explorer einzubetten, könnten Sie einfach ein ASP.NET-Steuerelement verwenden; das ist viel einfacher und effizienter. Doch wenn Sie ein eigenes Steuerelement entwickelt haben, dann wäre die geschilderte Methode eine gute Möglichkeit, es über das Web erreichbar zu machen. (Weitere Informationen zum Erstellen von benutzerdefinierten Steuerelementen erhalten Sie an Tag 18.)
432
Internetsicherheit
12.4 Internetsicherheit Internetsicherheit ist ein vielschichtiges Thema. Dieses Unterkapitel wird Sicherheitsaspekte nicht en détail abdecken können, denn dafür eignet sich ein (serverorientiertes) ASP.NET-Buch besser. Vielmehr befasst sich dieser Abschnitt damit, wie Windows FormsAnwendungen mit Sicherheit umgehen. Heutzutage befinden sich mehrere Formen von Internetsicherheit in Gebrauch, wobei jede einem anderen Zweck dient. Secure Sockets Layer (SSL) ist ein Protokoll (ähnlich wie HTTP), das für die sichere Datenübermittlung vom Client zum Server verwendet wird. Wahrscheinlich haben Sie bereits SSL im Zusammenhang mit Websites wie www.amazon.de benutzt, die Kreditkarteninformationen verarbeiten. Sie erkennen solche Sites daran, dass ihr URI stets mit dem Kürzel HTTPS (s wie secure) beginnt. Die Objekte WebRequest und WebResponse sind vollständig kompatibel zu SSL und Sie brauchen nichts Besonderes zu tun, um SSL nutzen zu können. Denken Sie daran, dass diese Objekte austauschbare Protokolle sind, so dass Ihre Anwendung bereits SSL handhaben kann. Ein weiterer Sicherheitstyp besteht in der Internetauthentifizierung. Anders als SSL, das sich auf die sichere Datenübertragung konzentriert, beruht Authentifizierung auf der Verifizierung der Berechtigungen des Clients, bevor überhaupt eine Datenübertragung erfolgt. Dieser Vorgang nimmt an, dass der Client nach der Bestätigung der Benutzeridentität auch das Recht hat, auf alle nötigen Ressourcen zuzugreifen – die sichere Übertragung wird dann durch andere Mechanismen (wie zum Beispiel SSL) gehandhabt. Sicherheit ist auch für Websites ein bedeutendes Thema, denn sie sind Hackerangriffen stärker ausgesetzt als Desktop-Anwendungen. Deshalb werden Websites oftmals mit besonderen Sicherheitsvorkehrungen geschützt. Zum Glück ist Windows Forms vollständig kompatibel mit solchen Maßnahmen, so dass Sie sichere Ressourcen mit Ihren Anwendungen erreichen können.
Authentifizierung Windows verfügt über drei Arten von Prozeduren der Internetauthentifizierung. Jede versucht, die Anmeldeinformationen des Benutzers zu verifizieren, bevor er in eine Website gelassen wird; die Site muss herausfinden, ob ein Benutzer der ist, der er zu sein vorgibt. Die gebräuchlichste Prozedur ist die Basisauthentifizierung. Bei dieser Methode fordert eine besuchte Website den Benutzer auf, einen Benutzernamen und sein Kennwort anzugeben. Stimmen diese Anmeldeinformationen mit den Angaben in der Serverliste der sicheren Benutzer überein, wird der Zugang gewährt. Abbildung 12.6 zeigt eine typische durch Basisauthentifizierung geschützte Website, wie in Windows XP zu sehen.
433
Formulare Internet-fähig machen
Abbildung 12.6: Basisauthentifizierung fordert Sie auf, Ihren Benutzernamen und Ihr Kennwort einzugeben.
Die zweite Methode, die als Digestauthentifizierung bezeichnet wird, ähnelt der ersten: Die Website fragt vom Benutzer Anmeldeinformationen ab. Der Unterschied besteht darin, dass bei der Basisauthentifizierung die Benutzereingaben unverschlüsselt zum Server geschickt werden, bei der Digestauthentifizierung aber verschlüsselt (im DigestModus). Die dritte Methode ist die Integrierte Windows-Authentifizierung (auch als NTLM bekannt). Dieses Verfahren ist recht raffiniert. Es findet nämlich keine Prüfung von Anmeldeinformationen statt, sondern die Clientversion von Windows kommuniziert hinter den Kulissen mit der Serverversion, um Informationen zu liefern. Die verwendeten Anmeldeinformationen sind jene, die Sie verwenden, wenn Sie sich in Ihren Computer einloggen. Dieses Verfahren ist recht sicher, doch es setzt voraus, dass beide Seiten Windows einsetzen.
Berechtigungsklassen Windows Forms und die WebRequest-Methode können mit allen drei Authentifizierungsverfahren zusammenarbeiten. Dafür ist nur eine Eigenschaft nötig, nämlich WebRequest.Credentials. Sie übernimmt als Parameter ein NetworkCredential-Objekt. WebRequest objRequest = WebRequest.Create("http://localhost/secure"); objRequest.Credentials = new NetworkCredential("Christopher Payne", "mein Kennwort");
Der Konstruktor für das NetworkCredential-Objekt übernimmt einen Benutzernamen, ein Kennwort und optional eine Domäne. Dann fordern Sie ganz normal eine Ressource (im Internet) an. Fertig. Dieses Verfahren funktioniert mit Basis-, Digest- und NTLM-Methoden. Alternativ können Sie Folgendes verwenden:
434
Zusammenfassung
WebRequest objRequest = WebRequest.Create("http://localhost/secure"); NetworkCredential objCredentials = new NetworkCredential(); objCredentials.UserName = "Christopher Payne "; objCredentials.Password ="Mein Kennwort"; objCredentials.Domain ="Meine Domäne "; objRequest.Credentials =objCredentials;
Sicherheit kann ein kompliziertes Thema sein, doch der Umgang damit fällt mit Windows Forms leicht.
12.5 Zusammenfassung Heute haben Sie gelernt, dass Windows Forms für seine Webanwendungen austauschbare Protokolle verwendet. Das sind Klassen, die die verschiedenen Internetprotokolle handhaben (HTTP, FTP etc.), die sich dynamisch je nach der angeforderten Internetressource ersetzen lassen. Auf diese Weise brauchen Sie nur einen Codesatz zu schreiben, um eine beliebige Zahl von Protokollen nutzen zu können. Den Kern der austauschbaren Protokolle bilden die Klassen WebRequest und WebResponse. Die beiden Klassen kapseln alle Funktionen, die man für den Zugriff auf Webressourcen benötigt, und führen die Protokollverarbeitung für Sie durch. Die Vorgehensweise für den Zugriff auf Internetressourcen besteht im Allgemeinen darin, mit Hilfe der WebRequest.Create-Methode und einem bereitgestellten URI ein WebRequest-Objekt zu erzeugen und dann die GetResponse-Methode aufzurufen. Darauf folgt GetResponseStream, um die Serverantwort abzuholen, und zum Schluss das Beenden der Verbindung. Optional können Sie GetRequestStream aufrufen, um Informationen auf den Server zu schreiben. Windows Forms-Anwendungen können auf verschiedene Weise mit Internet Explorer arbeiten. Sie können einfach eine Anwendung mit ihrem URL aufrufen oder sie mit Hilfe des OBJECT-Tags in eine Webseite einbetten. Zum Schluss haben Sie gelernt, dass alle Aspekte der Authentifizierungssicherheit über die Credentials-Eigenschaft des WebRequest-Objekts gehandhabt werden. Die Eigenschaft übernimmt ein NetworkCredential-Objekt, das Benutzername, Kennwort und Domäne als
Informationen an einen Server schickt.
435
Formulare Internet-fähig machen
12.6 Fragen und Antworten F
In welcher Hinsicht unterscheidet sich eine ASP.NET-Anwendung von einer webfähigen .NET-Windows-Anwendung? A
F
Kann ich asynchrone Webanforderungen einsetzen? A
F
ASP.NET-Anwendungen sind speziell für das Internet entworfen und lassen sich nur über einen Server erreichen. Bei ASP.NET wurde die verteilte Struktur des Internet berücksichtigt, wodurch es in Sachen Internet weitaus effizienter als Windows Forms ist. Aus diesem Grund bevorzugt man für Serveranwendungen ASP.NET gegenüber Windows Forms. Das soll Sie aber nicht davon abhalten, Clientanwendungen in einer der beiden Technologien zu entwickeln.
Na klar! Genau wie bei normaler E/A haben die Webklassen von Windows Forms auch asynchronen Zugriff, der auch genauso verwendet wird. Die asynchrone Arbeitsweise liegt jedoch jenseits der Themen dieses Buches. Weiterführende Informationen finden Sie in der .NET Framework-Dokumentation.
Was versteht man unter einem Webdienst? A
Webdienst (Web Service) ist die Bezeichnung für eine breite Kategorie von Internetanwendungen. Der Zweck von Webdiensten ist die unsichtbare Bereitstellung von Anwendungen (oder Teilen davon) über das Internet, und zwar auf dem Desktop des Endanwenders (nicht unbedingt in einer Browser-Oberfläche). Webdienste haben sowohl Kritiker als auch Unterstützer, die sich darüber streiten, ob in Webdiensten die Zukunft des Internet liegt.
12.7 Workshop Dieser Workshop soll Ihnen helfen, sich besser an die heute behandelten Begriffe zu erinnern. Es hilft Ihnen sehr, die Antworten vollständig zu verstehen, bevor Sie weiterlesen. Die Antworten zu den Quizfragen und den Übungen finden Sie in Anhang A.
Quiz 1. Wahr oder falsch? Um dem Server POST-Informationen zu schicken, verwendet man das WebResponse-Objekt. 2. Nennen Sie die vier Bestandteile eines URI. 3. Welchen Typs muss der Inhalt sein, um Formularinformationen posten zu können?
436
Workshop
4. Was ist das HttpWebRequest-Objekt? 5. Welche sind die drei Typen der Windows-Authentifizierung?
Übung Erstellen Sie eine Anwendung, die eine Webseite (Ihrer Wahl) untersucht, deren Bilder parst (in HTML beginnen Grafiken mit dem Tag Sortieren ->"; btSort.Location = new System.Drawing.Point(300,250); btSort.Size = new Size(150,50); btSort.Click += new EventHandler(this.Sort); cboUnsorted.Location = new System.Drawing.Point(50,50); cboUnsorted.Size = new Size(150,500); cboUnsorted.DropDownStyle = ComboBoxStyle.Simple; lbSorted.Location = new System.Drawing.Point(550,50); lbSorted.Size = new Size(150,500); this.Text = "Listing 14.3"; this.Size = new Size(800,600); this.Controls.Add(btSort); this.Controls.Add(btAdd); this.Controls.Add(cboUnsorted); this.Controls.Add(lbSorted); } private void Add(Object Sender, EventArgs e) { cboUnsorted.Items.Add(cboUnsorted.Text); cboUnsorted.Text = ""; } private void Sort(Object Sender, EventArgs e) { lbSorted.Items.Clear(); int intCount = cboUnsorted.Items.Count; objApp = new Excel.Application(); colWBooks = objApp.Workbooks; objWBook = colWBooks.Add(XlWBATemplate.xlWBATWorksheet); colSheets = objWBook.Worksheets; objSheet = (_Worksheet) colSheets.get_Item(1);
493
ActiveX
62:
Range objRange = objSheet.get_Range("A1", Convert.ToChar(intCount+64).ToString() + "1");
63: 64: 65: 66: 67: 68: 69: 70:
String[] arrValues = new String[intCount]; for (int i = 0; i < intCount; i++) { arrValues[i] = cboUnsorted.Items[i].ToString(); } objRange.Value2 = arrValues; objRange.Sort(objSheet.Rows[1, Missing.Value], XlSortOrder.xlAscending, Missing.Value, Missing.Value, XlSortOrder.xlAscending, Missing.Value, XlSortOrder.xlAscending, XlYesNoGuess.xlNo, Missing.Value, Missing.Value, XlSortOrientation.xlSortRows, XlSortMethod.xlStroke);
71: 72: 73: 74: 75:
Object[,] arrSorted; arrSorted = (Object[,])objRange.Value2; for (int i=arrSorted.GetLowerBound(0); i public void Submit(Object Sender, EventArgs e) { lblMessage.Text = "Willkommen " + tbName.Text + "!
Es " +"ist " + System.DateTime.Now.ToString(); } Bitte geben Sie Ihren Namen ein:
Speichern Sie die Datei unter dem Namen Listing15.1.aspx im Ordner c:\inetpub\wwwroot und betrachten Sie sie in Ihrem Browser mit Hilfe der Adresse http://localhost/ listing15.1.aspx. Nachdem Sie Ihren Namen eingegeben und die ABSCHICKEN-Schaltfläche angeklickt haben, sollten Sie einen Bildschirm sehen können, der Abbildung 15.3 ähnelt.
521
Webdienste
Abbildung 15.3: Ausgabebeispiel einer einfachen ASP.NET-Seite
Eine ASP.NET-Seite ist in zwei Hauptabschnitte eingeteilt: den Codedeklarationsblock (Zeilen 3 bis 9) und HTML (Zeilen 11 bis 20). Der Codedeklarationsblock ist in script-Tags eingehüllt. Das sieht aus wie typischer Windows Forms-Code in C#. Die Submit-Methode ist ein einfacher Ereignishandler (inklusive der üblichen Parameterliste), der den Text eines Label-Steuerelements auf einen Willkommensgruß setzt. Beachten Sie runat="server" in Zeile 3. Dies ist für eine ASP.NET-Seite sehr wichtig – ohne diese Angabe würde die Seite nicht richtig funktionieren. Der HTML-Abschnitt besteht aus unkompliziertem HTML sowie einigen für ASP.NET spezifischen Tags, insbesondere ASP:TextBox in Zeile 15, ASP:Button in Zeile 16 und ASP:Label in Zeile 18. Es handelt sich um ASP.NET-Steuerelemente, die den entsprechenden Steuerelementen in Windows Forms sehr ähnlich sind. Sie verfügen über die gleichen Eigenschaften und Ereignisse. (Beachten Sie, dass jedes das Attribut runat="server" aufweist.) Zeile 17 weist die ASP.NET-Seite an, die Submit-Methode auszuführen, sobald das ButtonSteuerelement angeklickt wurde. (Sie müssen die OnClick-Methode überschreiben, statt dem Ereignis einen Delegaten zuzuweisen.) Diese Seite wird im Browser als normale Webseite angezeigt, wenn sie betrachtet oder abgerufen wird. Klickt der Benutzer auf die ABSCHICKEN-Schaltfläche, verwendet die Seite jedoch eine HTTP-Post-Funktion, um Informationen an den Server zu schicken, wo sie wie
522
Mit ASP.NET Webdienste erstellen
eine Windows Forms-Anwendung ausgeführt wird. Ist dies beendet, schickt sie das Ergebnis in Form von HTML zurück an den Benutzer. Ein Webdienst arbeitet sehr ähnlich wie diese Seite: Ein Benutzer (Website, Anwendung oder reale Person) fordert den Webdienst von einem Browser oder einer Anwendung aus an, doch statt HTML zu liefern, gibt der Webdienst XML zurück. Wenn dann der Benutzer beschließt, eine Methode auszuführen (ähnlich wie der Klick auf die ABSCHICKENSchaltfläche), wird die Information via SOAP (oder HTTP-Get bzw. HTTP-Post) an den Server geschickt und die Ergebnisse kommen wieder als XML zurück.
Webmethoden Schauen wir uns einen sehr einfachen Webdienst an, nämlich einen Addier-Taschenrechner, der in Listing 15.2 zu sehen ist. Listing 15.2: Ein einfacher Webdienst 1: 2: 3: 4: 5: 6: 7: 8: 9:
using System.Web.Services; public class Calculator : WebService { [WebMethod] public int Add(int intA, int intB) { return intA + intB; } }
Speichern Sie diese Datei als Listing15.2.asmx (alle Webdienstdateien enden auf .asmx) im Ordner c:\inetpub\wwwroot. Diese Datei ähnelt einer Kreuzung aus einer ASP.NET-Seite und einer Windows Forms-Anwendung. Die erste Zeile einer Webdienstdatei deklariert, dass es sich um einen Webdienst handelt, welche Sprache er verwendet und die Hauptklasse des Dienstes. Zeile 3 importiert den Namensraum System.Web.Services – ein weiteres Erfordernis. Die Calculator-Klasse ist in Zeile 5 zu sehen. (Sie ist von der WebService-Klasse abgeleitet statt von der Form-Klasse wie normale Windows Forms-Anwendungen.) Als Nächstes kommt der wichtigste Teil eines Webdienstes: die Webmethode. Eine Webmethode (genauer: Webdienstmethode) verhält sich wie jede andere Methode, nur dass sie sich eben über das Internet ausführen lässt. Eine Methode lässt sich zur Webmethode umwandeln, indem man ihrer Deklaration das Attribut [WebMethod] anhängt, wie in Zeile 6 gezeigt (verwenden Sie in
523
Webdienste
VB .NET die Syntax <WebMethod()>). Man kann in einem Webdienst so viele Webmethoden einsetzen, wie man will, doch sie müssen alle das Attribut [WebMethod] aufweisen und als public deklariert sein. Soll eine Methode nicht über das Internet erreichbar sein, wird sie ganz normal deklariert. Beachten Sie besonders die Klasse und ihre Methode: Calculator und Add. Wenn Sie später Webdienste in Anwendungen integrieren, werden Sie wieder auf sie stoßen. Zum Glück hält .NET Werkzeuge für den Fall bereit, dass Sie diese Mitgliedernamen vergessen sollten (später mehr dazu). Die Funktionsweise dieses Webdienstes ist recht einfach. Wenn die AddMethode aufgerufen wird, übernimmt sie zwei Parameter, addiert sie und liefert das Ergebnis – genau wie jede reguläre Methode. Hier handelt es sich jedoch um eine Webmethode, die über das Internet von einem beliebigen Ort aus erreichbar ist. Hätte Microsoft diesen Addierdienst entwickelt, könnte man auf die Anwendung über die Website von Microsoft zugreifen, falls man zwei Zahlen addieren müsste (was zwar etwas praxisfremd ist, aber zumindest das Wesen der Methoden umreißt, die über das Internet zugänglich sind). Die .asmx-Datei lässt sich in einem Browser darstellen, genau wie eine ASP.NET-Seite. Der Grund dafür ist, dass eigentlich ASP.NET die Maschine ist, die Webdienste und .asmx-Dateien verarbeitet. In ASP.NET sind alle nötigen Funktionen eingebaut, um Webanforderungen anzustoßen und zu beantworten, so dass es als durchaus sinnvoll erscheint, Webdienste mit ASP.NET zu bündeln. Webdienstdateien enthalten keine Elemente für die Benutzeroberfläche. Die Implementierung der Benutzeroberfläche bleibt völlig dem Client überlassen; Sie müssen also lediglich die Funktionen erstellen, die hinter den Kulissen arbeiten. Obwohl dies eine sehr einfache Methode ist, können Sie schon erahnen, welche Möglichkeiten sich hier auftun. Angenommen, diese Klasse wäre in Wirklichkeit die Klasse Word.Application oder Word.Document, die Sie an Tag 14 nutzten. Mit diesem Webdienst könnten Sie jede verfügbare Webmethode von Word über das Internet nutzen!
Die Dienstbeschreibung Betrachten Sie die Datei Listing15.2.asmx in Ihrem Browser nach der Eingabe der Adresse http://localhost/listing15.2.asmx; Sie sollten dann die Seite sehen, die in Abbildung 15.4 wiedergegeben ist.
524
Mit ASP.NET Webdienste erstellen
Abbildung 15.4: Unsere Anwendung stellt die Beschreibung des Webdienstes zur Verfügung.
Dieser Text befand sich nicht in der .asmx-Datei, aber ASP.NET versteht, dass es sich um einen Webdienst handelt, und erzeugt eine zusätzliche Ausgabe für Sie. Die Seite in Abbildung 15.4 bietet eine nützliche Beschreibung für denjenigen, der den Webdienst nutzen möchte. Klicken Sie auf den Hyperlink »Dienstbeschreibung« oben auf der Seite, erhalten Sie den XML-Inhalt, den dieser Webdienst an potenzielle Kunden schickt. Er teilt mit, was dieser Webdienst kann, welche Daten er erwartet und welche er liefert. Dieser Code wird als Dienstbeschreibung (Service Description) bezeichnet und ist in der Web Services Description Language (WSDL) geschrieben – wobei es sich lediglich um eine einfallsreiche Methode handelt, XML zu formatieren. Wenn Sie XML schon kennen, werden Ihnen einige interessante Aspekte an diesem Code auffallen. Jede der Webdienst-Methoden (obwohl es momentan nur eine gibt) wird aufgelistet, nebst den Eingabeparametern, die sie erwartet. Es gibt zudem einen Abschnitt namens AddResponse, der die Antwort beschreibt, die der Webdienst schickt, sobald die AddMethode ausgeführt wird. Informationen zu den weiteren Abschnitten finden Sie auf der Website www.w3c.org/TR/wsdl. Die WSDL-Dienstbeschreibung spielt später noch eine Rolle, wenn Sie diesen Webdienst von einer Windows Forms-Anwendung aus nutzen wollen.
525
Webdienste
Webdienste unter ASP.NET Gehen Sie zurück auf die Beschreibungsseite, die in Abbildung 15.4 zu sehen ist, und klicken Sie auf die ADD-Verknüpfung am Kopf der Seite, die Sie auf eine neue Seite führt. Hier können Sie den Webdienst tatsächlich mit HTTP-Post ausprobieren. Geben Sie zwei Werte in die Textfelder ein, die mit intA und intB bezeichnet sind, und klicken Sie auf die INVOKE-Schaltfläche. Sie erhalten die folgende Ausgabe: 8
Hier handelt es sich um den XML-Inhalt, der zum Client des Webdienstes zurückgeliefert würde. Das Beispiel ist sehr einfach, doch die Ausgabe kann durchaus vielschichtig werden, da XML ja viele verschiedene Datentypen handhaben kann, darunter auch ADO.NET-Datasets. Wenn Sie XML nicht kennen, ist auch das kein Problem, weil Sie nämlich nie direkt mit XML zu tun bekommen, es sei denn, Sie wünschen es. Wie Sie in den nächsten Abschnitten sehen, kann das .NET Framework alles für Sie handhaben. Sie müssen lediglich entscheiden, welchen Webdienst Sie nutzen wollen, und ihn dann einsetzen. Alle Meldungen werden unsichtbar verarbeitet. Es erscheint Ihnen, als würden Sie eine weitere lokale Klasse oder ein solches Objekt in Ihrer Anwendung verwenden. Das ist einer der großartigen Nutzeffekte von Webdiensten. Beachten Sie, dass Datenübertragungen bis jetzt (die Ausführung der Add-Methode, das Betrachten der WSDL-Beschreibung, der Empfang der XML-Ausgabe) mit Hilfe eines Standardprotokolls ausgeführt worden sind: HTTP. Selbst die SOAP-Methoden (zu denen wir gleich kommen) »reisen« via HTTP. Da HTTP ein überall verfügbarer Standard ist, eignet sich dieses Protokoll ideal für Webdienst-Übertragungen.
15.3 Webdienste in Anwendungen Die Nutzung eines Webdienstes durch eine Windows Forms-Clientanwendung umfasst drei Schritte: Ermitteln (Discovery), Proxy und Implementierung. Ermitteln ist ein Vorgang, bei dem man versucht herauszufinden, über welche Fähigkeiten ein Webdienst verfügt – ansatzweise haben Sie das schon getan, als Sie die WSDL-Beschreibung untersucht haben. Der zweite Schritt besteht in der Erzeugung einer Proxy-Klasse, um den Dienst zu nutzen, und der dritte Schritt besteht darin, den Dienst tatsächlich zu implementieren, indem Sie von Ihrer Anwendung aus Befehle an ihn senden. Abbildung 15.5 veranschaulicht diese drei Interaktionen zwischen einem Webdienst und seinem Client. Die zwei letzten Schritte werden weiter unten beschrieben.
526
Webdienste in Anwendungen
1. Entdeckung Anwendung
Webdienst Was tust du? Beschreibung
2. Dienstbeschreibung Was erwartest du? Beschreibung 3. Befehle Tu dies Hier sind die Ergebnisse
Abbildung 15.5: Die Interaktion mit einem Webdienst umfasst das Ermitteln, das Untersuchen der Dienstbeschreibung und das Senden von Befehlen.
Ermitteln ist die Gelegenheit für den Client, den Webdienst kennen zu lernen. Man findet heraus, wozu der Dienst in der Lage ist, also welche Webmethoden er besitzt, und wie man diese Methoden ausführt. Ein Weg zur Ermittlung führt über die Dienstbeschreibung, welche Sie finden können, indem Sie die Zeichenfolge ?WSDL an das Ende des URLs des Dienstes anhängen, etwa so: http://localhost/listing15.2.asmx?WSDL
Das sieht zwar toll aus, hilft Ihrer Windows Forms-Anwendung aber nicht viel. Was wir brauchen, ist eine echte XML-Datei, die sich von einer potenziellen Clientanwendung nutzen lässt. Diese Datei kann man mit Hilfe des Web Service Discovery-Werkzeugs (disco.exe) erzeugen. Diesem Werkzeug muss man nur den URL des Webdienstes bereitstellen, dann erledigt es den Rest. Geben Sie in der Befehlszeile folgendes Kommando ein: disco http://localhost/listing15.2.asmx
Sie sollten folgende Ausgabe sehen können: Microsoft (R)Web Services Discovery Utility [Microsoft (R).NET Framework,Version 1.0.3705.0] Copyright (C)Microsoft Corporation 1998-2001.All rights reserved. Bei folgenden URLs wurden Dokumente gefunden: http://localhost/listing15.2.asmx?wsdl http://localhost/listing15.2.asmx?disco Folgende Dateien enthalten den Inhalt, der bei den entsprechenden URLs gefunden wurde: .\listing15.wsdl <sampleSection setting1="Wert1" setting2="Wert zwei" setting3="dritter Wert" />
So konfigurieren Sie Ihre Anwendungen
Im Augenblick brauchen Sie hierzu nur zu wissen, dass .NET mehrere XML-Elemente in der .config-Datei definiert. Sie können außerdem Ihre eigenen benutzerdefinierten Konfigurationsdateien erzeugen, damit Ihre Anwendung jede Einstellung findet, die sie braucht. Auch das .NET Framework hat eine Konfigurationsdatei. Sie ist normalerweise im Verzeichnis c:\winnt\Microsoft.NET\Framework\version\CONFIG abgelegt. Sie trägt den Namen machine.config, da sie die Einstellungen enthält, die der gesamte Computer verwendet. Wenn man keine eigenen anwendungsspezifischen Einstellungen angibt, so erbt die Anwendung solche von machine.config (das ist auch der Grund, warum nicht jede Ihrer Anwendungen eine .config-Datei benötigt). Diese Datei umfasst Informationen darüber, welche Klassen sich um Sicherheitsaspekte kümmern, welche Dateien mit der ASP.NETEngine verknüpft sind, welche Sprache und Kultur der Benutzer auf seinem Computer als Standard eingetragen hat, wie Debugging zu handhaben ist usw. Die Datei machine.config ist recht umfangreich, da sie Einstellungen für jeden Aspekt im .NET Framework umfasst. (Standardmäßig sind es mehr als 750 Zeilen.) Dieses Buch geht auf die Datei nicht näher ein, aber sehen Sie die Datei ruhig einmal durch.
20.2 So konfigurieren Sie Ihre Anwendungen Bevor man mit der Erzeugung von Konfigurationsdateien beginnen kann, sollte man zuerst eine zu konfigurierende Anwendung dafür haben. Listing 20.3 zeigt die Grundstruktur für eine Anwendung mit einem INFO-Menü. Listing 20.3: Eine konfigurationsfertige Anwendung 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16:
using System; using System.Windows.Forms; using System.Drawing; namespace TYWinforms.Day20 { public class Listing203 : Form { private MainMenu mnuMain = new MainMenu(); private MenuItem miHelp = new MenuItem("Hilfe"); private MenuItem miAbout = new MenuItem("Info"); public Listing203() { mnuMain.MenuItems.Add(miHelp); miHelp.MenuItems.Add(miAbout); miAbout.Click += new EventHandler(this.ShowAbout);
659
Windows Forms konfigurieren und bereitstellen
17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29:
this.Text = "Meine konfigurierbare Anwendung"; this.Menu = mnuMain; } private void ShowAbout(Object Sender, EventArgs e) { MessageBox.Show("TYWinforms Listing 20.3 Beispiel"); } public static void Main() { Application.Run(new Listing203()); } } }
Die Anwendung weist nur drei Steuerelemente auf: zwei MenuItems und ein MainMenu. Das INFO-Menüelement verfügt über einen Ereignishandler, der nur eine MessageBox mit einer netten Beschreibung des Programms anzeigt. Speichern Sie dieses Listing als Listing20.3.cs und kompilieren Sie es. Sie erhalten eine Datei namens Listing20.3.exe. Die entsprechende Konfigurationsdatei sollte demnach Listing20.3.exe.config heißen. Die Konfigurationsdatei muss im selben Ordner wie die ausführbare Datei liegen. War Ihre Anwendung im Verzeichnis c:\winforms\day20, so muss die Datei Listing20 .3.exe.config im selben Ordner liegen.
Konfigurationsabschnitte Die einfachste .config-Datei sieht so aus:
Das einzige erforderliche XML-Tag ist das -Element. Alle anderen Einstellungen sind – jeweils in ihren eigenen Elementen – innerhalb von platziert. So ist etwa ein weiteres gebräuchliches Konfigurationselement. Fügt man es dem vorigen Code hinzu, sieht das so aus:
660
So konfigurieren Sie Ihre Anwendungen
Bei der Erstellung dieser .config-Dateien sind einige Vorsichtsmaßnahmen zu ergreifen. Da es sich um XML-Dateien handelt, erfordern sie strikt angewandte XML-Syntax. Jedes Anfangstag zieht ein Endtag nach sich, etwa so: ... — oder —
Zweitens sind alle Abschnitte in der .config-Datei in der Camel-Schreibweise (alternierende Groß-/Kleinschreibung) zu formulieren. Dabei wird der erste Buchstabe des ersten Wortes im Abschnittsnamen klein geschrieben und alle nachfolgenden Wörter groß (falls nicht durch einen Punkt abgetrennt). Ein Beispiel: ... <webRequestModules /> ... <system.diagnostics />
Obwohl diese Regeln einfach sind, so bringen sie doch manchen Anfänger aus dem Konzept. Bitte schenken Sie ihnen Beachtung. Sollte einmal eine Konfigurationsdatei Ihre Anwendung zum Absturz gebracht haben, dann prüfen Sie noch einmal die Groß-/Kleinschreibung sowie die Klammern. Tabelle 20.1 führt alle Konfigurationsabschnitte auf, die für Windows Forms zur Verfügung stehen. Abschnitt
Beschreibung
Enthält Informationen über entfernte Objekte, die Ihre Anwendung in Anspruch nimmt und exponiert.
Lässt sich für die Unterbringung benutzerdefinierter Anwendungseinstellungen nutzen.
Legt die Module fest, die zur Authentifizierung von Internet-Anfragen benutzt werden.
Enthält Kanäle, über die die Anwendung mit entfernten Objekten kommuniziert.
Enthält Vorlagen für die Verarbeitung von Ereignissen aus Kanälen zu entfernten Objekten.
Definiert die Abschnitte in einer Konfigurationsdatei wie auch die Module und Assemblies, die diese Abschnitte verarbeiten.
Legt fest, wie viele Internetverbindungen höchstens zulässig sind.
Tabelle 20.1: Konfigurationsabschnitte für Windows Forms
661
Windows Forms konfigurieren und bereitstellen
Abschnitt
Beschreibung
<debug>
Legt fest, ob alle Klassen, die in der .config-Datei enthalten sind, beim Anwendungsstart geladen werden sollen (um bei der Validierung der .config-Datei zu helfen).
<defaultProxy>
Legt den Proxyserver fest, der für HTTP-Anforderungen an das Internet zu benutzen ist.
<mscorlib>
Enthält Kryptographie-Informationen.
Enthält Informationen über Assemblies und Garbage Collection.
<startup>
Enthält Informationen über die Version des .NET Frameworks, die für Ihre Anwendung benutzt werden soll.
<system.diagnostics>
Legt fest, dass Informationen in Dateien geschrieben werden, sofern die Ablaufverfolgung aktiviert ist (mehr dazu morgen).
<system.net>
Dient als Behälter für die Abschnitte , <defaultProxy>, und <webRequestModules>.
<system.runtime.remoting>
Dient als Behälter für die Abschnitte , , und <debug>.
<system.windows.forms>
Legt fest, wie Windows Forms-Anwendungen zu debuggen sind.
<webRequestModules>
Legt Module fest, die benutzt werden, um Internetanforderungen auszuführen.
Tabelle 20.1: Konfigurationsabschnitte für Windows Forms (Forts.)
Der Rest dieses Abschnitts erörtert die gängigsten vordefinierten Konfigurationselemente. Jedes Mal, wenn Sie eine Konfigurationsdatei ändern, müssen Sie erst die Anwendung neu starten, bevor die Änderungen wirksam werden.
Der Abschnitt Wir besprechen den -Abschnitt zuerst, weil er einer der wichtigsten ist. Wie in Tabelle 20.1 erwähnt, listet dieser Abschnitt die verschiedenen anderen Abschnitte auf und sagt, wie sie zu behandeln sind. Daher findet man für jeden nachfolgenden Abschnitt, der erörtert wird, einen Eintrag dafür im -Abschnitt in der machine.config-Datei. Zum Beispiel:
662
So konfigurieren Sie Ihre Anwendungen
<section name="runtime" type="System.Configuration.IgnoreSectionHandler, System, Version=1.0.3300.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" allowLocation="false"/> <section name="mscorlib" type="System.Configuration.IgnoreSectionHandler, System, Version=1.0.3300.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" allowLocation="false"/>
Das scheint eine Menge verwirrender Code zu sein, doch wir müssen ihn nicht vollständig verstehen. Das name-Attribut jedes hier definierten Abschnitts bestimmt den Namen dieses Abschnitts. Das type-Attribut zeigt die Klasse an, die zur Behandlung aller Aspekte dieses Abschnitts benutzt wird (etwa Lesen/Schreiben), gefolgt von der Assembly, in der sich die Klasse befindet, der Version dieser Assembly und einigen ergänzenden Informationen. Müssen Sie jemals Werte aus einem bestimmten Abschnitt beschaffen, schauen Sie in das -Element, um herauszufinden, welcher Klassentyp zur Lieferung dieser Werte zu erwarten ist. Die IgnoreSectionHandler-Klasse beispielsweise liefert keinerlei
Informationen; Abschnitte, die Benutzer nicht modifizieren sollten, verwenden diese Klasse. In ähnlicher Weise setzt der -Abschnitt eine Klasse des Typs NameValueFileSectionHandler ein, die eine Gruppe von Schlüssel/Wert-Paaren liefert, die die Informationen über die fragliche Anwendung darstellen. ist im Grunde nur bei zwei Gelegenheiten nützlich: wenn Sie wissen
müssen, wie ein bestimmter Abschnitt verarbeitet wird, und wenn Sie eigene Abschnitte definieren müssen. Um Letzteres zu erledigen, brauchen Sie lediglich den Namen des eigenen Abschnitts und die Klasse, die ihn handhabt, anzugeben. So erzeugt etwa das folgende Stück Code einen neuen Abschnitt namens »meineAnwendung«, die Schlüssel/Wert-Paare an Informationen aus Ihrer .config-Datei liefert: <section name="meineAwendung" type="System.Configuration.SingleTagSectionHandler" />
Der neue Abschnitt sollte folgendermaßen aussehen: <myApplication setting1="Wert1" />
663
Windows Forms konfigurieren und bereitstellen
Kümmern Sie sich nicht um die Klassen wie etwa SingleTagSectionHandler, die hier angegeben wurden. Man benutzt sie niemals direkt und Ihre Wahl, welche nun bei der Erstellung von Abschnitten zu bevorzugen ist, dürfte meist ohne Bedeutung sein. Das heutige Unterkapitel »Konfigurationswerte abrufen« erörtert die Klassen, über die Sie Bescheid wissen sollten. Sie können Ihre eigenen Klassen erstellen, um Konfigurationsabschnitte zu verarbeiten. Das erfordert das Anlegen einer Klasse, die die Schnittstelle IconfigurationSectionHandler implementiert und XML aus Ihren .config-Dateien lesen kann. Doch dies geht über das Thema dieses Buches hinaus. Mehr Informationen finden Sie in der Dokumentation zum .NET Framework.
Der Abschnitt Der -Abschnitt ist in Windows Forms-Anwendungen am häufigsten vorzufinden. Es handelt sich um einen benutzerdefinierten Konfigurationsabschnitt, den man nutzen kann, um beliebige Informationen zu speichern. Sie haben bereits ein Beispiel dafür gesehen:
Die Konfigurationsdaten in werden in Schlüssel/Wert-Paaren angeordnet. Mit Hilfe des add-Elements können Sie je nach Bedarf so viele dieser Paare erzeugen, wie Sie brauchen. Ihre Anwendung kann leicht darauf zugreifen (mehr dazu im Unterkapitel »Konfigurationswerte abrufen«). Das obige Codestück definiert einen neuen Schlüssel namens AppName mit dem Wert Listing 20.3. Wie Sie sicher bereits vermuten, legt diese Einstellung den Namen unserer Anwendung fest. Leicht lässt sich ein weiteres Element hinzufügen, um die Versionsnummer anzugeben: ... ...
Da sich nicht viel mehr über diesen Abschnitt sagen lässt, gehen wir zum nächsten.
664
So konfigurieren Sie Ihre Anwendungen
Der Abschnitt <startup> Wollen Sie angeben, dass Ihre Windows Forms-Anwendung eine bestimmte Version des .NET Frameworks benutzt, so können Sie dies im <startup>-Abschnitt tun. Weil aber die einzige verfügbare Framework-Version die Nummer 1.0 trägt, ist <startup> vorerst von keinem großen Nutzen. Erst wenn Microsoft weitere Versionen auf den Markt gebracht hat, erhält dieser Abschnitt mehr Sinn. Angenommen, die Version 2.0 würde veröffentlicht und verfügte über viele neue Funktionen. Ihre Anwendung erstellen Sie auf der Basis dieser Version und sie enthält nun Merkmale, die in Version 1.0 nicht verfügbar waren. Im <startup>-Abschnitt können Sie nun festlegen, dass Ihre Anwendung nur ausgeführt werden darf, wenn Version 2.0 vorliegt. Wenn der Benutzer versucht, die Anwendung auszuführen, ohne dass er Version 2.0 installiert hat, lässt sich die Anwendung einfach nicht laden und zeigt eine Fehlermeldung an. Die Syntax für diesen Abschnitt lautet wie folgt: <startup> <requiredRuntime version="version" safemode="safemode"
/>
Sie brauchen sich lediglich um die zwei Platzhalter version und safemode zu kümmern. version stellt natürlich die Version des zu verwendenden .NET Frameworks dar. Diese Angabe muss im Format vx.x.x.x vorliegen, wobei jeder x-Wert eine Zahl zwischen 0 und 65.535 ist. Safemode kann entweder true oder false sein und gibt an, ob die CLR die Windows-Registrierung nach der richtigen zu verwendenden Version durchsuchen soll. Ein Beispiel: <startup> <requiredRuntime version="v1.0.0.0" safemode="false" />
Beachten Sie, dass version und safemode nicht zwingend erforderlich sind. Gibt man dennoch eine version-Nummer und true für safemode an, dann muss die angegebene Versionsnummer mit der in der Windows-Registrierung übereinstimmen, sonst wird ein Fehler erzeugt.
665
Windows Forms konfigurieren und bereitstellen
Der Abschnitt <system.windows.forms> Dieser Abschnitt legt nur eines fest: welches Element Fehler behandeln soll, die in einer Anwendung auftreten. Ein kurzer Blick auf die Syntax: <system.windows.forms jitDebugging="value" />
Das .NET Framework benutzt ja einen JIT-Compiler (vgl. Tag 1), um Anwendungen aus MSIL in Maschinencode zu kompilieren, wenn die Anwendung ausgeführt wird. Die Anwendung wird also gerade noch rechtzeitig kompiliert, damit sie ausgeführt werden kann. Ähnlich arbeitet auch ein JIT-Debugger. Er greift »gerade noch rechtzeitig« ein, um Ihnen dabei zu helfen, die Anwendung zu reparieren, sobald ein Fehler aufgetreten ist. Stoßen Sie in einer Windows Forms-Anwendung auf Fehler, dann haben Sie zwei Alternativen. Die erste Option besteht darin, nichts zu tun. Standardmäßig erhält man in Windows Forms folgende Fehlermeldung, wie in Abbildung 20.2 zu sehen.
Abbildung 20.2: Der standardmäßige Fehlerbehandlung in Windows Forms
Das in Abbildung 20.2 gezeigte Dialogfeld bietet ein wenig Aufklärung darüber, was den Fehler verursachte, lässt aber keine Aktion Ihrerseits zu. Sie können entweder fortfahren, die Anwendung zu nutzen, oder sie beenden, wobei Sie praktisch den Fehler ignorieren. Die zweite Wahlmöglichkeit besteht im Einsatz des JIT-Debuggers zur Fehlerbeseitigung. Ein JIT-Debugger erlaubt es Ihnen, einen Fehler in der Anwendung zu beheben (sofern Sie noch über den Quellcode verfügen), so dass der Fehler nicht noch einmal auftreten kann. In der Standardeinstellung hat Windows Forms das »Eingreifen« eines JIT-Debuggers zur Fehlerbeseitigung deaktiviert. Hier kommt der Konfigurationsabschnitt <system.windows.forms> ins Spiel. Haben Sie im obigen Code value auf true gesetzt, dann erscheint die in Abbildung 20.2 zu sehende Meldung nicht, sondern vielmehr sehen Sie etwas wie in Abbildung 20.3. In diesem Fall wird Ihnen eine Auswahl an JIT-Debuggern geboten, um einen Fehler zu beheben.
666
So konfigurieren Sie Ihre Anwendungen
Abbildung 20.3: Wenn Sie JIT-Debuggen aktivieren, können Sie einen JIT-Debugger zur Fehlerbeseitigung einsetzen.
In der morgigen Lektion wird Debugging intensiv behandelt. Damit der Abschnitt <system.windows.forms> funktioniert, müssen Sie zunächst Ihre Anwendung mit aktiviertem Debugging kompiliert haben. Die Aktivierung wird mit der Option /debug vorgenommen: vbc /t:winexe /r:system.dll /debug MeinListing.vb Dies erzeugt eine neue Datei mit der Endung .pdb. Die Datei enthält spezielle
Informationen über die fragliche Anwendung, die Debugger auswerten können.
Konfigurationswerte abrufen Nachdem Sie Ihre .config-Datei erstellt und eingerichtet haben, kann die Anwendung auf diese Konfigurationswerte leicht mit der ConfigurationSettings-Klasse zugreifen. Deren zwei Mitglieder werden hier vorgestellt. Das erste ist die GetConfig-Methode. Man setzt sie ein, indem man einen abzurufenden Konfigurationsabschnitt angibt. Der folgende Code liefert die Informationen, die im Abschnitt <system.windows.forms> enthalten sind: ConfigurationSettings.GetConfig("system.windows.forms")
667
Windows Forms konfigurieren und bereitstellen
Die Klasse ConfigurationSettings ist im Namensraum System.Configuration enthalten. Diesen müssen Sie unbedingt importieren, wollen Sie ConfigurationSettings verwenden. Das Code-Statement liefert ein Objekt vom Typ System.Windows.Forms.ConfigData, das aber leider nicht sonderlich nützlich ist. Die meisten Konfigurationsabschnitte geben solche Objekte zurück. Das ist der Grund, warum der Konfigurationsabschnitt eingeführt wurde. Dieser Abschnitt liefert ein benutzerfreundliches Objekt, was uns zum zweiten Mitglied der ConfigurationSettings-Klasse führt. Die AppSettings-Eigenschaft liefert passenderweise die Einstellungen, die in enthalten sind. Statt GetConfig zu benutzen und die gelieferten Objekte umwandeln zu müssen, präsentiert AppSettings alle Daten in leicht verwendbarem Format. Erstellen Sie eine .config-Datei für die aus Listing 20.3 erzeugte Anwendung (listing20.3.exe) und taufen Sie sie listing20.3.exe.config. Fügen Sie sie dem Code hinzu, der in Listing 20.4 zu sehen ist. Listing 20.4: Die .config-Datei für Listing 20.3 1: 2: 3: 4: 5:
6: 7:
Nun ändern Sie Zeile 17 des Listings 20.3 wie folgt: this.Text = ConfigurationSettings.AppSettings["AppName"] + " version " + ConfigurationSettings.AppSettings["Version"];
Dieser Code ruft die Informationen von »AppName« und »Version« aus der .config-Datei ab und zeigt sie in der Titelzeile der Anwendung an. Ändern Sie Zeile 22 wie folgt: MessageBox.Show(ConfigurationSettings.AppSettings["Beschreibung"]);
Dieser Code zeigt die »Beschreibung«-Einstellung im INFO-Fenster an. Kompilieren Sie die Anwendung neu (und vergessen Sie nicht, den Namensraum System.Configuration zu importieren). Klicken Sie auf das INFO-Menü. Sie sollten etwas Ähnliches wie in Abbildung 20.4 sehen.
668
Lokalisieren Sie Ihre Anwendung
Abbildung 20.4: Sie können Ihre Konfigurationsdaten in Ihrer Anwendung anzeigen lassen.
Das war's schon. Verwenden Sie die AppSettings-Eigenschaft, um in eckigen Klammern (in VB .NET in runden Klammern) die Einstellung anzugeben, an der Sie interessiert sind und schon wird sie beschafft (und angezeigt, wenn Sie wollen).
20.3 Lokalisieren Sie Ihre Anwendung Lokalisierung ist der Prozess, bei dem bestimmte Aspekte je nach Publikum einer Anwendung in kulturspezifische Versionen umgewandelt werden, so etwa in französische Sprache statt englische, wenn die Anwendung in Frankreich benutzt wird. Windows pflegt die jeweiligen Ländereinstellungen eines Benutzers und eine Anwendung kann diese Einstellungen ermitteln und sich ihnen automatisch anpassen. Lokalisierung klingt nach einem komplizierten Vorgang, ist es aber nicht. Das .NET Framework macht es Ihnen leicht, die Ländereinstellungen für eine Anwendung zu ändern. Sie müssen lediglich noch die eigentlichen Informationen in die jeweils gewünschte Sprache übersetzen. Meistens sind Texte und Grafiken zu übersetzen. In Begriffen der Lokalisierung werden solche Elemente als Ressourcen bezeichnet. Sie lassen sich getrennt von der Anwendung speichern. Bei der Lokalisierung einer Anwendung brauchen Sie lediglich diese Ressourcen zu übersetzen und .NET übernimmt den Rest.
669
Windows Forms konfigurieren und bereitstellen
Lokalisierung ist ein vierstufiger Prozess: 1. Erstellen Sie in normalem Text die Ressourcendateien, die für die verschiedenen Länder gebraucht werden. 2. Konvertieren Sie die Ressourcendateien in .resource-Dateien. 3. Kompilieren Sie die Ressourcendateien zu einer Assembly und legen Sie die Assemblies in entsprechenden Verzeichnissen ab. 4. Greifen Sie aus Ihrer Anwendung auf die Ressourcendateien zu. Der erste Schritt ist der einfachste. Öffnen Sie den Windows-Editor und geben Sie den Text des Listings 20.5 ein. Listing 20.5: Ressourcen für Englisch 1: 2: 3:
Caption = TYWinforms Day 20 Application WelcomeMessage = Welcome to localization techniques! GoodbyeMessage = Leaving so soon?
Speichern Sie das Listing als Day20.txt. Dann erzeugen Sie eine Eigenschaft und einen entsprechenden Wert in Ihrer Anwendung. In unserem Fall wurden die Eigenschaften Caption, WelcomeMessage und GoodbyeMessage mit angemessenem Text erzeugt, der an verschiedenen Stellen im Programm angezeigt wird. Nun erstellen Sie eine weitere Ressource, aber diesmal in Spanisch, wie in Listing 20.6 zu sehen. Listing 20.6: Ressourcen für Spanisch 1: 2: 3:
Caption = TYWinforms Applicacion Dia 20 WelcomeMessage = ¡Bienvenidos a tecnicas de localizacion! GoodbyeMessage = Ya se va?
Speichern Sie diese Datei unter Day20.es-ES.txt. Sie sollten bei diesem Listing ein paar Aspekte beachten, zuerst den Dateinamen. Der Anfang ist identisch mit dem für Listing 20.5: »Day 20«. Das »es« ist der Code für die spanische Ländereinstellung. Einige dieser Codes zeigen Unterkulturen an. »es-GT« steht für guatemaltekisches Spanisch, »es-ES« für Spanisch, das in Spanien gesprochen wird. In Listing 20.5 wurde dieser Kulturcode weggelassen, da es sich um die Standardkultur handelte: in diesem Fall Englisch. Ist Englisch nicht der Standard für die Ländereinstellungen, dann lautet sein Kürzel »en«. Die richtige Bezeichnung dieser Dateien ist sehr wichtig. Sie werden gleich verstehen, warum. (Eine komplette Liste der Kulturcodes finden Sie in der Dokumentation des .NET Frameworks unter der CultureInfo-Klasse.)
670
Lokalisieren Sie Ihre Anwendung
Beachten Sie, dass die Eigenschaftsnamen die gleichen geblieben sind. Das erleichtert es Entwicklern, auf sie im Code zu verweisen. Lediglich der eigentliche Textwert muss geändert werden. Nun sind die Ressourcendateien in etwas zu kompilieren, das das .NET Framework gebrauchen kann. Das ist ein zweistufiger Vorgang. Der erste Schritt besteht darin, das Werkzeug Resource File Generator einzusetzen (resgen.exe), um die .txt-Dateien in .resource-Dateien umzuwandeln. Die Syntax ist einfach: resgen txt_Datei resource_Datei
In diesem Fall haben Sie zwei Befehle: resgen Day20.txt Day20.resources resgen Day20.es-ES.txt Day20.es-ES.resources
Das Ergebnis sollte wie folgt aussehen: Read in Writing Read in Writing
3 resources from 'Day20.txt' resource file... Done. 3 resources from 'Day20.es-ES.txt' resource file... Done.
Wenn Sie Visual Studio .NET einsetzen, um Anwendungen zu lokalisieren, denken Sie daran, dass die von Visual Studio .NET erzeugten Ressourcendateien nicht automatisch zu den von resgen.exe erzeugten kompatibel sind. Haben Sie sich erst einmal für eine Methode entschieden, können Sie also nicht wechseln, ohne wieder ganz von vorne anzufangen. Der nächste Schritt besteht darin, diese .resources-Dateien zu einer Assembly zu kompilieren. Die dafür empfohlene Methode sieht vor, die Standardressourcendateien (hier in Englisch) in die ausführbare Anwendung einzubetten und alle anderen Ressourcendateien zu separaten Assemblies zu kompilieren (hier für Spanisch), die als Satellitenassemblies bezeichnet werden. Wenn man der Anwendung eine neue Ressource hinzufügen muss, muss man auf diese Weise nicht die ausführbare Datei anfassen, sondern lediglich eine neue Ressource anlegen und sie in einer Assembly im richtigen Verzeichnis zusammen mit der fraglichen Anwendung speichern. Um eine Ressource in die ausführbare Datei einzubetten, verwendet man einen CompilerBefehl von der Befehlszeile (csc für C#, vbc für VB .NET) zusammen mit dem /resourceParameter.
671
Windows Forms konfigurieren und bereitstellen
Um jedoch Satellitenassemblies aus den Ressourcendateien zu erstellen, die nicht zum Standard gehören, setzt man den Assembly Linker ein (al.exe). Die Syntax für dieses Hilfsprogramm sieht wie folgt aus: al /t:library /embed:filename /culture:culture /out:out_file
Der erste Parameter, filename, stellt die .resources-Datei dar, die zu kompilieren ist. In diesem Fall soll filename Day20.es-ES.resources sein. Der zweite Parameter, culture, gibt die Kultur für eine bestimmte zu kompilierende Datei an. Das ist wichtig, denn man kann nur eine Ressourcendatei pro Assembly haben, und jede Assembly muss den gleichen Namen haben (gleich mehr dazu). Die einzige Möglichkeit, wie das .NET Framework herausfinden kann, welche Assembly zu welcher Kultur gehört, besteht also im Einsatz der culture-Eigenschaft. Der letzte Parameter, out_file, ist der Dateiname der kompilierten Assembly (eine .dll-Datei). In unserem Fall sollten Sie den folgenden Befehl verwenden: al /t:library /embed:Day20.es-ES.resources /culture:es-ES /out:Day20.resources.dll
Sie erhalten eine Datei namens Day20.resources.dll, die die Spanisch-Ressourcen enthält. Die Englisch-Ressourcen sind ja in der Datei Day20.resources abzulegen. Ein kleiner Exkurs zu den Dateinamen: Es ist bei der Lokalisierung in .NET wichtig, dass alle Ihre Dateinamen übereinstimmen. So haben etwa Ihre zwei Ressourcendateien den gleichen Anfang: Day20. Auch die Satellitenassembly teilt diese Wurzel und gleich werden Sie sehen, dass dies auch die Anwendung tun muss. .NET verlässt sich auf diese Namenskonvention, um beim Aufruf die richtigen Ressourcen finden zu können. Zusätzlich muss man auch die richtige Verzeichnisstruktur für die Ressourcen erstellen. Für jede vorhandene Satellitenassembly, die nicht dem Standard angehört, sollte man ein eigenes Verzeichnis mit dem Kulturnamen erzeugen. In unserem Fall haben Sie eine Ressourcendatei für die Kultur es-ES. Deshalb legen Sie im Ordner Ihrer Anwendung einen Unterordner namens es-ES an und legen hier Ihre kompilierte Ressourcenassembly ab. Abbildung 20.5 zeigt eine typische Verzeichnisstruktur. c:\Winforms\Day20 Day20.exe Day20.resources es-ES Day20.resources.dll Day20.es-ES.resources fr Day20.resources.dll Day20.fr.resources …
672
Abbildung 20.5: Ihre kompilierten Quelldateien sollten in den Ordnern ihrer jeweiligen Kultur abgelegt werden.
Lokalisieren Sie Ihre Anwendung
Soll eine Anwendung die Ländereinstellungen ändern, sucht sie zuerst in jedem KulturVerzeichnis nach der richtigen Ressourcenassembly. Steht keine zur Verfügung, werden die Standardressourcen verwendet. Gibt es einen Ordner, der zwar die Kultur, aber nicht die Unterkultur angibt, dann verwendet die Anwendung statt dessen die übergeordnete Kultur. Hat man beispielsweise eine Satellitenassembly und ein Verzeichnis nur für allgemeines Spanisch (es) angelegt, doch die Kultur des Benutzers ist es-ES, dann greift die Anwendung auf die es-Ressourcen zurück. Dieser Vorgang wird als Ressourcenfallback bezeichnet. Nun benötigen Sie für die Nutzung Ihrer Ressourcen nur noch eine Anwendung. Listing 20.7 zeigt eine einfache Anwendung, die Ihre kompilierte Ressourcenassembly-Datei ausliest. Listing 20.7: Ihre erste Anwendung, die die jeweilige Kultur berücksichtigt 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30:
using using using using using using using using
System; System.Windows.Forms; System.Drawing; System.Resources; System.Reflection; System.Threading; System.Globalization; System.ComponentModel;
namespace TYWinforms.Day20 { public class Listing207 : Form { private ResourceManager rsmDay20; private Label lblMessage = new Label(); public Listing207() { rsmDay20 = new ResourceManager("Day20", this.GetType().Assembly); lblMessage.Text = rsmDay20.GetString("Willkommensgruss"); lblMessage.Size = new Size(300,100); this.Text = rsmDay20.GetString("Titelzeile"); this.Controls.Add(lblMessage); this.Closing += new CancelEventHandler (this.CloseMe); } private void CloseMe(Object Sender, CancelEventArgs e) { MessageBox.Show(rsmDay20.GetString ("Abschiedgruss")); } public static void Main() {
673
Windows Forms konfigurieren und bereitstellen
31: 32: 33: 34:
Application.Run(new Listing207()); } } }
Speichern Sie dieses Listing als Day20.cs. (Dieser Dateiname muss mit dem Ihrer Ressourcendateien übereinstimmen.) In den Zeilen 1 bis 8 werden eine ganze Reihe zusätzlicher Namensräume importiert. Sie sind für die Lokalisierung und einige andere Dinge im Code erforderlich. In Zeile 12 wird die ResourceManager-Klasse erstellt, die sozusagen die Schaltstelle für die Interaktion mit Ressourcen bildet. ResourceManager verfügt über diverse Methoden, die Ressourcendateien laden und die darin enthaltenen Daten holen können. In Zeile 16 wird eine Instanz des ResourceManagerObjekts angelegt. Der Konstruktor übernimmt zwei Parameter. Der erste legt die Namenswurzel der zu verwendenden Ressourcen fest. (Jetzt verstehen Sie, warum alle Dateien die gleiche Wurzel haben müssen.) Der zweite Parameter besteht aus der Hauptassembly für die Ressourcen. Meist besteht diese Assembly einfach aus Ihrer zentralen ausführbaren Datei. GetType().Assembly liefert diejenige Assembly, die die Klasse Listing207 enthält. In Zeile 18 sehen Sie, wie eine Ressource das erste Mal geholt wird. Statt den Text des Bezeichnungsfeldes auf einen statischen Wert zu setzen, benutzen Sie die GetString-Methode der ResourceManager-Klasse und geben die Eigenschaft der zu holenden Ressourcendatei an. Denken Sie noch einmal an die Listings 20.5 und 20.6, in denen die Eigenschaften Caption, WelcomeMessage und GoodbyeMessage zur Verfügung standen. Die zweite wird in Zeile 18 eingesetzt, um den im Label-Steuerelement enthaltenen Text zu bestimmen. Die Caption-Eigenschaft hingegen wird in Zeile 21 verwendet, wieder mit der GetString-Methode des ResourceManager-Objekts. Da Sie dem Benutzer bei Beendigung der Anwendung eine Meldung anzeigen wollen, hängen Sie an das Closing-Ereignis einen Ereignishandler an. Die CloseMe-Methode in Zeile 26 zeigt eine MessageBox mit der GoodbyeMessage-Eigenschaft an. Denken Sie daran, dass Sie immer noch die standardmäßige Ressourcendatei haben: Day20.resources. Sie müssen sie in die kompilierte Version von Listing 20.7 einbetten. Zu diesem Zweck benutzen Sie folgenden Befehl, um Ihre Anwendung zu kompilieren: csc /t:winexe /r:system.dll /r:system.drawing.dll /r:system.windows.forms.dll / res:Day20.resources Day20.cs
Die einzige Neuheit hier ist der /res-Parameter (der für /resource steht). Er gibt die standardmäßige Ressourcendatei an, die in die ausführbare Datei einzubetten ist. Angenommen, Ihre Kultur ist Englisch, dann sehen Sie das in Abbildung 20.6 gezeigte Ergebnis,
674
Lokalisieren Sie Ihre Anwendung
wenn Sie versuchen, Ihre Anwendung zu schließen. Die in der Titelzeile, dem LabelSteuerelement und dem Meldungsfeld zu sehenden Werte wurden aus dieser Standardressourcendatei beschafft. Wollen Sie die Anwendung in einer anderen Kultur testen, stehen Ihnen zwei Optionen zur Wahl: Schicken Sie Ihre Anwendung in ein anderes Land und installieren Sie sie dort, oder verwenden Sie die CultureInfo-Klasse. Es könnte zwar erholsam sein, nach Spanien zu reisen, doch wesentlich schneller geht es mit der CultureInfo-Klasse.
Abbildung 20.6: Ihre lokalisierte Anwendung verhält sich in ihrer jeweiligen Kultur ganz normal.
Der Hauptthread in Ihrer Anwendung (vgl. Tag 19) verfügt über eine Eigenschaft CurrentUICulture, die bestimmt, welche Kultur und somit, welche Ressourcen die Anwendung verwenden soll. Die CultureInfo-Klasse lässt sich dazu benutzen, eine bestimmte Kultur vorzugeben. Um die Spanisch-Anwendung zu testen, fügen Sie folgenden Code zwischen die Zeilen 16 und 17 ein: Thread.CurrentThread.CurrentUICulture = new CultureInfo("es-ES");
Dieser Code stellt die Eigenschaft CurrentUICulture des Hauptthreads mit Hilfe der CultureInfo-Klasse auf es-ES ein. Kompilieren Sie die Anwendung (mit dem /res-Parameter) neu, dann sollten Sie das in Abbildung 20.7 gezeigte Ergebnis sehen. Alle Ressourcen wurden in Spanisch umgewandelt. Wenn man die Kultur mit der CultureInfo-Klasse ändert, schaut die Anwendung nach, ob im Ordner der ausführbaren Datei ein Unterverzeichnis namens es-ES existiert. Ist es vorhanden, besorgt sich die Anwendung ihre Ressourcen von dort. Ist das aber nicht der Fall, greift die Anwendung auf die Standardkultur zurück, nämlich Englisch.
675
Windows Forms konfigurieren und bereitstellen
Abbildung 20.7: Ihre Anwendung beherrscht nun mehrere Sprachen!
Wenn Sie eine Anwendung in eine weitere Sprache übersetzen müssen, erzeugen Sie einfache eine neue .resources-Datei, kompilieren sie zu einer Satellitenassembly und legen sie im richtigen Unterverzeichnis der Anwendung ab. Sie wiederholen also die Schritte 1 bis 3 vom Anfang dieser Lektion. Sie brauchen die Anwendung nicht neu zu kompilieren (es sei denn, Sie wollen sie mit unterschiedlichen CultureInfo-Objekten testen), was es erleichtert, die Anwendung zu lokalisieren. Es wird häufig die Empfehlung gegeben, all jene Elemente einer Anwendung, die sich lokalisieren lassen, in Ressourcendateien abzulegen, selbst dann, wenn man nicht vorhat, die Anwendung in andere Länder zu liefern. Wenn also eine Wortwahl oder ein Bild in der Anwendung zu ändern ist, kann man das tun, indem man nur die Ressourcendateien bearbeitet, statt die gesamte Anwendung neu kompilieren zu müssen.
20.4 Eingabehilfen Häufig müssen Sie Ihre Anwendung an die Bedürfnisse von Menschen mit Behinderungen oder Bedienungsproblemen bei Computern anpassen. Microsoft Active Accessibility (MSAA) ist eine Technologie, die es dem Entwickler erleichtern soll, solche Leistungsmerkmale einzubinden, die dieser Zielgruppe helfen. Standardmäßig setzt bereits jedes Windows Forms-Steuerelement MSAA um; jedes Steuerelement verfügt über mehrere Eigenschaften, die spezifisch die MSAA betreffen. Bestimmte Anwendungen, die als Clientanwendungen mit Eingabehilfen bezeichnet werden, benutzen diese Eigenschaften, um Behinderten zu helfen, häufig in Form von visuellen Hinweisen oder gesprochenen Beschreibungen. Tabelle 20.2 führt diese Eigenschaften auf.
676
Der einfache Teil der Arbeit: Bereitstellung
Eigenschaft
Beschreibung
AccessibilityObject
Liefert ein AccessibleObject-Objekt, das die Eingabehilfen des betreffenden Steuerelements beschreibt.
AccessibleDefaultActionDescription
Beschreibt die Aktion, die ein betreffendes Steuerelement ausführt.
AccessibleDescription
Beschafft oder setzt eine Beschreibung, die von Eingabehilfe-Clientanwendungen benutzt wird.
AccessibleName
Gibt den Namen des Steuerelements an, den die Eingabehilfe-Clientanwendungen zu sehen bekommen.
AccessibleRole
Gibt den Typ des betreffenden Steuerelements an, etwa ein Dialogfeld oder ein MenuItem.
IsAccessible
Gibt an, ob das betreffende Steuerelement zu Eingabehilfe-Clientanwendungen konform ist.
Tabelle 20.2: Eigenschaften für Eingabehilfen
Sie finden die verschiedenen Eingabehilfen-Clients, die in Windows eingebaut sind, wenn Sie auf START, PROGRAMME, ZUBEHÖR, EINGABEHILFEN oder in die SYSTEMSTEUERUNG gehen.
20.5 Der einfache Teil der Arbeit: Bereitstellung Bereitstellung ist das bei weitem einfachste Thema in der heutigen Lektion. Bereitstellung (Deployment) meint die Installation einer Anwendung auf den Computern der Benutzer. Da im .NET Framework Anwendungen vollständig autonom sind, besteht die einzige notwendige Maßnahme für ihre Bereitstellung im Kopieren von Dateien. Jede kompilierte .NET-Anwendung enthält ja Metadaten, die dem Computer alle nötigen Informationen liefern, um die betreffende Anwendung auszuführen. Eintragungen in der Registrierung vorzunehmen, Dateien ins Windows-Verzeichnis zu speichern oder etwas dergleichen aus der Zeit vor .NET ist nunmehr unnötig. Die erste Technik zur Bereitstellung besteht im Kopieren der entsprechenden Dateien auf den Zielcomputer. Bei den heute geschriebenen Anwendungen würde dies die .exeDateien betreffen. Hinzu kommen die Erstellung aller erforderlichen Unterverzeichnisse für andere Kulturen und die Platzierung passender Satellitenassemblies in ihnen. Der Kopiervorgang kann mit den gängigen Methoden erfolgen, vom File Transfer Protocol (FTP) bis hin zum guten alten XCOPY-Befehl.
677
Windows Forms konfigurieren und bereitstellen
Eine zweite Methode sieht den Einsatz der Windows Installer-Anwendung vor. Dieses Programm verpackt alle nötigen Dateien und erzeugt eine Datei mit der Endung .msi. Windows kann die .msi-Datei nutzen, um die Anwendung zu installieren (ähnlich wie das bekannte Programm Setup.exe, das sich in vielen älteren Anwendungen findet). Der Windows Installer nimmt auch Eintragungen im HINZUFÜGEN/ENTFERNEN-Applet der Systemsteuerung (SOFTWARE-Applet) vor, mit denen sich Anwendungen leicht von der Festplatte entfernen lassen. Eine dritte Methode besteht darin, die Anwendungsdateien zu einer .CAB-Datei zu komprimieren, die als Archivdatei bezeichnet wird. Solche Archivdateien sind lediglich gepackte Versionen der ursprünglichen Dateien, die sich bei Bedarf extrahieren lassen. Nähere Informationen über den Windows Installer und den Einsatz von Archivdateien finden Sie auf Microsofts Website unter dem folgenden URL: http://www.microsoft.com/msdownload/platformsdk/sdkupdate/psdkredist.htm
20.6 Zusammenfassung Konfiguration und Bereitstellung sind meist die letzten auszuführenden Schritte in der Anwendungsentwicklung. Ihre Behandlung am Ende des Buches ist daher angemessen. Die Konfiguration von Windows Forms-Anwendungen wird normalerweise mit .configXML-Dateien gehandhabt. Diese Dateien enthalten Einstellungen, die eine Anwendung benutzt, darunter Hinweise, welche Version des .NET Frameworks zu verwenden ist und wie man Unterstützung für Debugging bietet. Um auf diese Einstellungen über die Anwendung zuzugreifen, benutzt man die ConfigurationSettings-Klasse und entweder die Eigenschaft AppSettings, die Werte aus dem Konfigurationselement liefert, oder die GetConfig-Methode, um jeden anderen Abschnitt der .config-Datei zu liefern. Eine Anwendung zu lokalisieren, bedeutet, ihre Ressourcen in verschiedene Sprachen und Kulturen zu übertragen. Dazu gehört die Erzeugung von .resources-Dateien und Satellitenassemblies für jede weitere Kultur, die man unterstützen will. Das Bezeichnungsschema für lokalisierte Anwendungen ist von großer Bedeutung: Alle Dateien müssen den gleichen Wurzelnamen tragen. Man kann die ResourceManager-Klasse einsetzen, um Werte zu holen, die in Ressourcendateien enthalten sind. Auf diese Weise lässt sich die Anwendungslogik vollständig von der Benutzeroberfläche getrennt halten. Microsoft Active Accessibility ist in alle Windows Forms-Steuerelemente eingebaut. Die Eingabehilfen helfen Benutzern, die Schwierigkeiten bei der Bedienung einer Anwendung haben, indem sie ihnen audiovisuelle Hinweise geben, wie sie sich auf dem Computer zurechtfinden können. Diese Funktion wird in jedem Steuerelement durch eine Reihe besonderer Eigenschaften unterstützt.
678
Fragen und Antworten
Bereitstellung ist der Vorgang, bei dem eine Anwendung an die Benutzer ausgeliefert wird. Das einzige Erfordernis besteht hier im Kopieren der nötigen Dateien. Das .NET Framework und die CLR handhaben alles Weitere für Sie.
20.7 Fragen und Antworten F
Wie erzeuge ich Ressourcendateien aus Bildern? A
Leider ist dies schwieriger als bei reinem Text. Sie müssen erst Ihre Bilder in angemessen formatiertes XML umwandeln, bevor Sie sie zu Assemblies kompilieren können. Das .NET Framework QuickStart-Paket enthält ein Hilfsprogramm namens ResXGen.exe. Es benutzt folgende Syntax: ResXGen /i:dateiname /o:ressourcen_name /n:name
wobei dateiname die Grafikdatei angibt, ressourcen_name die zu erzeugende Ressourcendatei (mit der Endung .resx) und name den Eigenschaftsnamen für diese Ressource (ähnlich wie heute »WelcomeMessage«). Nähere Informationen über ResXGen finden Sie in der .NET Framework-Dokumentation. F
In der Zeit vor .NET konnte man benutzerspezifische Informationen in der Registrierung ablegen. Für eine einzelne Anwendung konnte ich so für verschiedene Benutzer jeweils unterschiedliche Einstellungen realisieren. Kann ich das auch in .NET tun? A
F
Mit .config-Dateien geht das nicht. Man kann aber weiterhin benutzerspezifische Informationen in der Windows-Registrierung mit Hilfe der Registry-Klasse pflegen. Diese Klasse liefert Informationen über in der Registrierung abgelegte Daten und erlaubt auch den Schreibzugriff auf die Registrierung.
Ich bekomme nicht heraus, welche Eigenschaft ein bestimmter Handler für einen Konfigurationsabschnitt hat, oder welchen Datentyp er liefert. Ich bitte um Hilfe. A
Das Hilfsprogramm Intermediate Language Disassembler (ildasm.exe) erweist sich als nützlich, um die Inhalte einer .NET Assembly zu untersuchen (.dll- oder .exe-Dateien). Das Werkzeug verfügt über eine grafische Oberfläche, die alle Mitglieder, die in einer Assembly enthalten sind, detailliert auflistet. So können Sie herausfinden, welche Typen das Objekt liefert und welche Methoden und Eigenschaften es unterstützt. Das Tool ist häufig ein guter Ersatz für die Dokumentation des .NET Frameworks.
679
Windows Forms konfigurieren und bereitstellen
20.8 Workshop Dieser Workshop soll Ihnen helfen, sich besser an die heute behandelten Begriffe zu erinnern. Es hilft Ihnen sehr, die Antworten vollständig zu verstehen, bevor Sie weiterlesen. Die Antworten zu den Quizfragen und den Übungen finden Sie in Anhang A.
Quiz 1. Welchen Vorteil bieten .config- gegenüber .ini-Dateien? 2. Was macht das Werkzeug al.exe? 3. Welche zwei Parameter erwartet das Hilfsprogramm resgen? 4. Wahr oder falsch? Eine Konfigurationsdatei für eine MyApp.exe genannte Anwendung sollte MyApp.config genannt werden. 5. Wahr oder falsch? Wenn man Ressourcendateien übersetzt, muss man auch die Eigenschaftsnamen wie auch deren Werte übersetzen. 6. Welches ist das Basiselement für alle .config-Dateien? 7. Zu welchem Zweck wird das Konfigurationselement <system.windows.forms> verwendet? 8. Was versteht man unter Camel-Schreibweise und wo ist sie erforderlich? 9. Schreiben Sie eine einfache .config-Datei, die über einen -Abschnitt verfügt. Dieser Abschnitt sollte ein Element namens Color mit dem Wert Black enthalten.
Übungen 1. Nehmen Sie drei weitere Ländereinstellungen in das Beispiel aus Listing 20.6 auf: Französisch (aus Frankreich), Dänisch und Griechisch. Kümmern Sie sich nicht um das Übersetzen der eigentlichen Ausdrücke, sondern ändern Sie sie nur so, dass sie die jeweils aktuelle Kultur widerspiegeln. (In der .NET Framework-Dokumentation zur CultureInfo-Klasse finden Sie Angaben zu den Kulturcodes für diese Sprachen.) 2. Erstellen Sie eine .config-Datei, die die jeweils zu verwendende Kultur von Übung 1 angibt. So müssen Sie nicht Ihren Quellcode verändern und die Anwendung jedes Mal kompilieren, wenn Sie eine neue Kultureinstellung testen wollen. (Tipp: Nutzen Sie den -Abschnitt.)
680
Debugging und Profiling
1 2
Debugging und Profiling
Die heutige Lektion dreht sich um Debugging und Profiling. Debugging ist der Vorgang, bei dem jegliche Fehler in einer Anwendung beseitigt werden, gleichgültig, ob sie Ihnen bekannt sind oder nicht. Profiling (oder Profilerstellung) ist eine Technik, um Anwendungen hinsichtlich ihrer Leistung zu optimieren, indem man beobachtet, welche Leistung sie unter normalen Bedingungen aufbringen. Diese zwei Themen wurden zusammengefasst, weil zu ihnen ähnliche Prozesse gehören. Zu beiden Verfahren gehört die Technik, eine laufende Anwendung zu überwachen, damit man herausfinden kann, was genau »unter der Motorhaube« vor sich geht, wenn ein Benutzer die Anwendung bedient. Heute lernen Sie 쐽
die Vorteile kennen, die der Einsatz von try-catch-Blöcken im Code bietet,
쐽
wie man Programmdatenbankdateien für das Debugging erzeugt,
쐽
auf welche Weise JIT-Debugging funktioniert,
쐽
Aspekte des CLR-Debuggers und seines Befehlszeilenvetters CorDbg kennen,
쐽
die Process-Klasse einzusetzen.
21.1 Debuggen in Windows Forms Ohne Zweifel sind Sie bei der Erstellung von Windows Forms-Anwendungen auch über Fehler gestolpert. Fehler gehören zum Alltag des Entwicklers, daher muss man lernen, damit umzugehen. Häufig ist der Vorgang der Fehlerbeseitigung (also Debugging) zeitraubend und lästig; man durchsucht den Code Zeile für Zeile und zuweilen sogar Zeichen für Zeichen. Das .NET Framework erleichtert jedoch das Beheben von Fehlern. Man kann seine Software auf verschiedene Weise debuggen. Dieser Abschnitt untersucht Möglichkeiten, wie Sie die Fehlerquelle einkreisen können, während Sie noch Code schreiben. Die folgenden Abschnitte stellen eine Reihe von Werkzeugen vor, die das .NET Framework bereitstellt, um Fehler nach der Anwendungsentwicklung zu beseitigen. Zunächst ist es sinnvoll, einen Blick auf die Debugging-Strukturen zu werfen, die .NET bereitstellt. Sobald ein Fehler nach dem Kompilieren einer Anwendung auftritt, löst die CLR eine Ausnahme (Exception) aus – ein einfallsreicher Ausdruck für einen Fehler. Tritt solch ein »Bug« auf, wirft (throws) die CLR eine Ausnahme. .NET verfügt über eine Exception-Klasse (und viele davon abgeleitete Klassen), die zur Darstellung jedes auftretenden Fehlers einsetzbar ist.
682
Debuggen in Windows Forms
Das ist sinnvoll, denn nun sind Fehler wirkliche Objekte, mit denen man umgeht. Man kann daher ihre Eigenschaften herausfinden, Methoden dafür aufrufen und sogar eigene Fehler erzeugen. Zur Fehlerbehandlung in Windows Forms gehört es, diese Exception-Klassen zu finden und sie angemessen zu behandeln. Der Rest dieses Abschnitts befasst sich mit der einfachsten Methode, Exception-Klassen zu handhaben: durch den Einsatz der try-catch-Syntax des .NET Frameworks. Try-catch-Syntax tritt in Blöcken auf. Der try-Teil bedeutet, dass man Code versucht bzw. versuchsweise ausführt. Setzt man einen try-Codeblock ein, wird die CLR vorsichtiger bei der Codeausführung. Sie wird dadurch nicht langsamer, doch wenn jetzt eine Ausnahme ausgelöst wird, ist die CLR vorgewarnt und verhindert einen Programmabsturz. Dies führt uns zum catch-Block: Er fängt jede Ausnahme ab, die ausgelöst wurde. Hier kann man jede Ausnahme bearbeiten und korrigieren. Da er mit dem try-Block kombiniert wird, lassen sich nun Fehlermeldungen besonnen behandeln, statt den Benutzer dadurch aufzuregen, dass seine Anwendung abstürzt.
Die Syntax für einen try-catch-Block ist einfach: try { //sensibler Code } catch (Exception e) { //Code für die Ausnahmebehandlung }
Oder in VB .NET: Try 'sensibler Code Catch e as Exception 'Code für die Ausnahmebehandlung End Try
Unter »sensiblem Code« versteht man einen Codeabschnitt, der eine Ausnahme auslösen kann oder von dem man glaubt, dass er nicht richtig funktionieren wird. Solcher Code kann alles Mögliche umfassen: von der Initialisierung einer Variablen bis hin zum Einsatz der E/ A-Fähigkeiten von .NET, um eine möglicherweise nicht vorhandene Datei zu öffnen. Das schon erwähnte catch-Statement fängt eine bestimmte Exception ab. Die zwei Codeabschnitte oben haben ein Objekt vom Typ Exception abgefangen. Man kann dies durch jede andere Klasse ersetzen, die von Exception abgeleitet ist, etwa IOException. Einzelne Ausnahmeklassen stellen bestimmte Fehlertypen dar und können eine Reihe von Unterausnahmen haben, die es erlauben, die Fehlerquelle weiter einzukreisen. Will man etwa auf eine nicht vorhandene Datei zugreifen, wird eine IOException ausgelöst, die von der Klasse SystemException abgeleitet ist, welche wiederum von Exception abgeleitet ist (vgl. Abbildung 21.1).
683
Debugging und Profiling
System. Exception System. ApplicationException*
System.Windows.Forms.AxHost.InvalidActiveXStateException
InvalidFilterCriteriaException TargetException TargetInvocationException TargetParameterCountException System.IO.IsolatedStorage.IsolatedStorageException
*All sub-exceptions belong to the System.Reflection namespace **All sub-exceptions belong to the System namespace, unless otherwise noted ←Belongs to the System. ComponentModel namespace →+ Belongs to the System.Runtime.InteropServices namespace +++ Belongs to the System.Runtime namespace ’Belongs to the System.Security namespace ”Belongs to the System.Threading namespace
System.Runtime.Remoting.MetadataServices.SUDSGeneratorException System.SystemException**
AppDomainUnloadedException ArgumentException ArithmeticException ArrayTypeMismatchException BadImageFormatException CannotUnloadAppDomainException Design.Serialization.CodeDOMSerializerException+ LicenseException+ WarningException+ Configuration.ConfigurationException Configuration.Install.InstallException ContextMarshallException Data.DataException Data.DBConcurrencyException Data.SqlClient.SqlException DataSqlTypes.SqlTypeException Drawing.Printing.InvalidPrinterException EnterpriseServices.RegistrationException EnterpriseServices.ServicedComponentException ExecutionEngineException FormatException IndexOutOfRangeException InvalidCastException
InvalidOperationException InvalidProgramException IO.InternalBufferOverflowException IO.IOException Management.ManagementException MemberAccessException MulticastNotSupportedException NotImplementedException NotSupportedException NullReferenceException OutOfMemoryException RankException Reflection.AmbiguousMatchException Reflection.ReflectionTypeLoadException Resources.MissingManifestResourceException ExternalException++ InvalidComObjectException++ InvalidOleVariantTypeException++ MarshalDirectiveException++ SafeArrayRankMismatchException++ SafeArrayTypeMismatchException++ Remoting.RemotingException+++ Remoting.ServerException+++
Serialization.SerializationException+++ Cryptography.CryptographicException’ Policy.PolicyException’ SecurityException’ VerificationException’ XmlSyntaxException’ ServiceProcess.TimeoutException StackOverflowException SynchronizationLockException” ThreadAbortException” ThreadInterruptedException” ThreadStateException” TypeInitializationException TypeLoadException TypeUnloadedException UnauthorizedAccessException Web.Services.Protocols.SoapException Xml.Schema.XmlSchemaException Xml.XmlException Xml.XPath.XPathException Xml.Xsl.XsltException
Abbildung 21.1: Die Ausnahmenhierarchie im .NET Framework ist recht kompliziert (System.SystemException verfügt sogar
über zwei oder gar drei Unterebenen, die hier nicht angezeigt sind).
Weil das Ausnahmeklassen von .NET hierarchisch gegliedert sind, kann man einen catchBlock für eine übergeordnete Ausnahme benutzen und zugleich alle Ausnahmen abfangen, die von der übergeordneten abgeleitet sind. In den zwei Codebeispielen oben fängt man eigentlich jede Art von Ausnahme ab, weil die Exception-Klasse deren »Urvater« ist. Das bedeutet, dass man mehrere catch-Blöcke pro try-Block einsetzen kann, sollte man einmal mehrere verschiedene Ausnahmetypen behandeln müssen. Beispielsweise so: try { //sensibler Code } catch (IOException e) { //eine E/A-Ausnahme abfangen } catch (SystemException e) { //jede System-Ausnahme abfangen, darunter auch IOException und ihre gleichgeordneten Ausnahmen } catch (Exception e) { //alle Ausnahmen abfangen, einschließlich System-Ausnahmen }
684
Debuggen in Windows Forms
Sorgen Sie dafür, dass Sie beim Einsatz mehrerer catch-Blöcke vom fokussiertesten zum diffusesten vorgehen: Code, der einen ganz bestimmten Fehler behandelt, sollte also vor Code kommen, der einen allgemein gehaltenen Fehler wie etwa Exception behandelt. Da nur ein catch-Block ausgeführt wird, könnte sonst die generische Fehlerbehandlung stets vor allen anderen ausgeführt werden. Zeit für ein Beispiel. Listing 21.1 zeigt eine einfache Anwendung, die versucht, eine Textdatei zu öffnen und ihren Inhalt in einem RichTextBox-Steuerelement anzuzeigen. Listing 21.1: Eine einfache E/A-Anwendung 1: Imports System 2: Imports System.Windows.Forms 3: Imports System.Drawing 4: 5: Namespace TYWinForms.Day21 6: 7: Public Class Listing211 : Inherits Form 8: private rtbText as New RichTextBox 9: private btPush as new Button() 10: 11: public sub New() 12: rtbText.Height = 225 13: rtbText.Dock = DockStyle.Bottom 14: rtbText.ScrollBars = RichTextBoxScrollBars.Both 15: 16: btPush.Location = new Point(100,10) 17: btPush.Text = "Öffnen!" 18: AddHandler btPush.Click, new EventHandler(AddressOf Me.OpenFile) 19: 20: Me.Text = "Listing 21.1" 21: Me.Controls.Add(rtbText) 22: Me.Controls.Add(btPush) 23: end sub 24: 25: private sub OpenFile(Sender As Object, e As EventArgs) 26: rtbText.LoadFile("c:\temp\happy.txt", RichTextBoxStreamType.PlainText) 27: end sub 28: 29: public shared sub Main() 30: Application.Run(new Listing211) 31: end sub 32: End Class 33: End Namespace
685
Debugging und Profiling
Nach Tag 11 dürfte dieses Listing recht vertraut aussehen. In den Zeilen 8 und 9 werden ein RichTextBox- und ein Button-Steuerelement erzeugt. Sie werden in den Zeilen 12 bis 18 initialisiert; das Click-Ereignis der Schaltfläche hat in Zeile 25 einen Ereignishandler, der die LoadFile-Methode des RichTextBoxSteuerelements benutzt, um die Datei c:\temp\happy.txt zu öffnen und sie dem Benutzer anzuzeigen. Ist diese jedoch nicht vorhanden und führt man die Anwendung so aus, erhält man die in Abbildung 21.2 gezeigte Fehlermeldung, falls man keinen Debugger installiert hat, oder die Fehlermeldung in Abbildung 21.3, falls man doch einen Debugger hat.
Abbildung 21.2: Die Standardroutine für die Ausnahmebehandlung lässt Ihnen die Möglichkeit, fortzufahren und den Fehler zu ignorieren oder die Anwendung zu beenden.
Abbildung 21.3: Falls Sie einen JIT-Debugger installiert haben, können Sie die Anwendung debuggen. Beachten Sie den Ausnahmetyp, der ausgelöst wird.
Wären Sie der Benutzer dieser Anwendung, dürften Sie angesichts der in Abbildung 21.2 bzw. 21.3 wiedergegebenen Meldung(en) ziemlich frustriert sein. Man muss den Fehler so abfangen, dass der Benutzer eine hilfreiche Fehlermeldung erhält, falls überhaupt. Ändern Sie die OpenFile-Methode in Zeile 25 des Listings 21.1 so ab, dass sie wie in folgendem Codestück aussieht:
686
Debuggen in Windows Forms
private sub OpenFile(Sender As Object, e As EventArgs) Try rtbText.LoadFile("c:\temp\happy.txt", RichTextBoxStreamType.PlainText) Catch ex As Exception MessageBox.Show("Diese Datei existiert nicht!") End Try end sub
Klickt der Benutzer nun auf die Schaltfläche und ist die Datei nicht vorhanden, erhält er eine Meldung wie in Abbildung 21.4.
Abbildung 21.4: Bei der Verwendung eines Try-Catch-Blocks können Sie eine weitaus benutzerfreundlichere Meldung erzeugen.
Klickt der Benutzer auf die OK-Schaltfläche, fährt die Anwendung fort, als wäre die Fehlermeldung nie ausgelöst worden – fast so, als wäre die OpenFile-Methode nie ausgeführt worden. Eine Meldung wird innerhalb des Catch-Blocks angezeigt, aber im Grunde könnte man hier alles Mögliche tun. So könnte man beispielsweise den Text abändern, um den Benutzer zu fragen, ob er die Datei erstellen möchte, falls es sie nicht gibt, und dies mit Hilfe der E/A-Fähigkeiten von .NET ausführen. Der try-catch-Block hat einen weiteren, zusätzlichen Teil: das finally-Statement. Dessen Syntax folgt der des catch-Statements. finally wird benutzt, um Code auszuführen, der immer laufen muss, gleichgültig, ob der Code in einem try-Block funktioniert oder nicht. Das folgende Codebeispiel erzeugt ein Array von Zeichenfolgen und versucht, ein FileInfo-Objekt aus jeder Zeichenfolge zu erzeugen. Ist die Datei vorhanden, wird eine Meldung mit der Angabe ihrer Länge angezeigt. Gibt es sie aber nicht, wird eine benutzerfreundliche Meldung gezeigt. Im Meldungsfeld befindet sich die Anzahl der verarbeiteten Zeichenfolgen, gleichgültig, ob ein FileInfo-Objekt erzeugt wurde oder nicht. int i = 0; FileInfo tmpFile; String[] arrFileStrings = new String[] {"c:\\temp\\happy.txt", "c:\\winforms\\day21\\listing21.1.vb"};
687
Debugging und Profiling
foreach (String strName in arrFileStrings) { try { tmpFile = new FileInfo(strName); MessageBox.Show("Länge von " + strName + ": " + tmpFile.Length.ToString()); } catch (Exception ex) { MessageBox.Show(strName + " ist keine gültige Datei!"); } finally { i++; MessageBox.Show(i.ToString() + " Dateien verarbeitet"); } }
Alle Ausnahmeklassen haben einige nützliche Eigenschaften, die zusätzliche Informationen über den betreffenden Fehler liefern. Tabelle 21.1 beschreibt die Eigenschaften der Exception-Klasse, die ja allen Unterausnahmeklassen vererbt werden. Eigenschaft
Beschreibung
HelpLink
Liefert eine optionale Datei, die mit der betreffenden Ausnahme verknüpft ist. Das Format muss ein URL sein. Zum Beispiel: file://C://TYWinforms/Day21/help.html#ErrorNum42
InnerException
Wenn eine Ausnahme eine weitere auslöst liefert diese Eigenschaft einen Hinweis auf die Ursprungsausnahme.
Message
Die benutzerfreundliche Meldung, die die Ausnahme beschreibt.
Source
Der Name der Anwendung oder des Objekts, das die Ausnahme erzeugte.
StackTrace
Die Zeichenfolgendarstellung aller Stapelrahmen in der Aufrufliste zum Zeitpunkt der Ausnahmeauslösung. Diese Eigenschaft teilt Ihnen mit, welche Methoden gerade ausgeführt wurden. (Eine Methode ruft oftmals eine weitere auf, so dass Sie nun der Spur zurück bis zur Wurzelmethode des Problems folgen können.)
TargetSite
Liefert die Methode, die die Ausnahme verursachte.
Tabelle 21.1: Eigenschaften von Exception
Statt zu warten, bis eine Fehlermeldung auftritt, können Sie auch jederzeit Ihre eigenen Fehler erzeugen, indem Sie das throw-Statement verwenden. Erzeugen Sie einfach eine neue Ausnahme vom gewünschten Typ und schon lässt sich Ihre eigene Anwendung sabotieren: throw(new Exception());
Warum sollte man so etwas tun, fragt man sich. Das tut man am ehesten, wenn man benutzerdefinierte Windows Forms-Steuerelemente erstellt hat und auch das Verhalten im Fehlerfall prüfen möchte. Das Schlüsselwort throw eignet sich ausgezeichnet für diesen
688
JIT-Debugging
Zweck. Man kann die Message- und InnerException-Eigenschaften für neu erstellte Ausnahmen festlegen, die die behandelnde Anwendung dann ebenso benutzen kann, wie Sie es bereits mit den eingebauten Ausnahmen getan haben. Im Allgemeinen sollte man einen try-catch-Block immer dann einsetzen, wenn man Code ausführt, von dem nicht sicher ist, dass er garantiert sicher ausgeführt wird, wie etwa Datenbankabfragen, Dateioperationen, Netzwerkanforderungen und Operationen, die von bestimmten Benutzereingaben abhängen. Im ganzen Buch wurden try-catch-Blöcke an den meisten Stellen, wo sie sich hätten befinden sollen, weggelassen, doch da Sie nun wissen, wie man deren Syntax benutzt, sollten Sie nicht zögern, sie einzusetzen. Ich empfehle
Bitte beachten Sie
Setzen Sie try-catch-Blöcke immer dann ein, wenn Sie Code ausführen, von dem Sie nicht sicher sind, dass er richtig ausgeführt wird.
Verlassen Sie sich nicht auf try-catch-Blöcke, um Benutzereingaben auf Gültigkeit zu prüfen. Wenn Sie dem Benutzer ein Textfenster anzeigen, in das er eine Zahl eintragen soll, ist zu prüfen, ob die Eingabe wirklich eine Zahl ist, bevor man den Code ausführt, der von der Zahl abhängt. Versuchen Sie nicht, solchen Code auszuführen, in der Hoffnung, dass der trycatch-Block ihn schon richtig behandeln werde.
21.2 JIT-Debugging Just-In-Time-Debugging ist, wie bereits erwähnt, der Vorgang, bei dem die Fehler in der Anwendung beseitigt werden, während diese gerade läuft und die Fehler nacheinander auftreten. Während try-catch-Blöcke dafür geeignet sind, Bugs zu beseitigen, bevor der Benutzer sie zu sehen bekommt, wurde JIT-Debugging dafür entworfen, den Benutzer auf die Fehler aufmerksam zu machen, damit sie beseitigt werden können. In diesem Fall ist der Benutzer natürlich der Entwickler (der einzige, der die Fehler beheben kann). Läuft die Anwendung, führt der Computer kompilierten Code aus. Dieser Code besteht aus Anweisungen, erzeugt bei der Ausführung Variablen, weist diesen Werte zu und hebt sie auch wieder auf. Alle diese Variablen werden natürlich im Speicher abgelegt. Wenn man sagt, ein Debugger wird angehängt, so ist damit gemeint, dass ein JIT-Debugger den Speicher überwacht, den die betreffende Anwendung belegt. Der Debugger kann die Anweisung und den belegten Speicher auswerten und dem Entwickler in verständlicher Form mitteilen, was los ist.
689
Debugging und Profiling
Weil der JIT-Debugger nicht das Innenleben der gerade laufenden Anwendung kennen kann (da er nicht auf deren Quellcode zugreifen kann), braucht er etwas Unterstützung, um Informationen liefern zu können. Diese Hilfe bekommt er von einer Programm-Datenbankdatei (.pdb). Sie enthält Debugging-relevante Informationen wie etwa über Methoden und Variablen im Programm. Eine .pdb-Datei zu erstellen, ist leicht: Fügen Sie den /debug-Parameter in die Befehle für den Befehlszeilencompiler ein: vbc /t:winexe /r:system.dll,... /debug temp.vb
Durch dieses Kommando wird eine neue Datei namens temp.vb erzeugt. Versucht ein JITDebugger Fehler in einer Anwendung zu beseitigen – in diesem Fall in temp.exe –, benutzt er den Inhalt der .pdb-Datei, um ihm zu helfen. Zur Aktivierung des JIT-Debuggings ist noch ein weiterer Schritt nötig. Erinnern Sie sich an .config-Dateien und das Konfigurationselement <system.windows.forms>. Hierin ist auch JIT-Debugging zu aktivieren. Beispielsweise könnte die .config-Datei für temp.exe wie folgt aussehen: <system.windows.forms jitDebugging="true"/>
Als Nächstes lernen Sie alle .NET-Werkzeuge kennen, die für das Debugging zur Verfügung stehen.
Das Werkzeug DbgClr Der Common Language Runtime Debugger (DbgClr), erlaubt das Anhängen an einen Prozess und so die Überwachung und Kontrolle der Ausführung einer Anwendung. Dieses Programm ist üblicherweise im Verzeichnis c:\Programme\Microsoft.NET\FrameworkSDK\GuiDebug abgelegt und heißt DbgClr.exe. Sie können sowohl diesen Debugger an eine bereits laufende Anwendung anhängen als auch eine neue Anwendung aus dem Debugger heraus starten. Im zweiten Fall startet man ganz von vorn und öffnet den Debugger, indem man DbgClr.exe ausführt. Sie sollten etwas sehen, das wie das in Abbildung 21.5 Gezeigte aussieht. Nachdem Sie den Debugger geöffnet haben, führen Sie die drei Schritte des DebuggingProzesses aus: 1. Öffnen Sie die zu bereinigende Anwendung oder hängen Sie den Debugger an die bereits laufende Anwendung an. 2. Setzen Sie Haltepunkte. 3. Setzen Sie den Debugger ein, um die Anwendung zu bearbeiten.
690
JIT-Debugging
Abbildung 21.5: Die Benutzeroberfläche des CLR-Debuggers ähnelt ein wenig der von Visual Studio .NET.
Vorausgesetzt, Sie haben Listing 21.1 als listing21.1.vb gespeichert, öffnen Sie die Datei bitte aus dem CLR-Debugger heraus, indem Sie in das Menü DATEI/ÖFFNEN/DATEINAME gehen und diese Datei auswählen. Nach dem Öffnen sollten Sie den Quellcode im Debugger sehen. Die Datei erscheint im PROJEKTMAPPEN EXPLORER auf der rechten Seite des Fensters. In dieser Datei können Sie den Debugging-Prozess verfolgen. Um mit der Fehlerbeseitigung zu beginnen, wählen Sie ZU DEBUGGENDES PROGRAMM aus dem DEBUGGEN-Menü aus und geben den Namen der ausführbaren Datei ein (c:\winforms\day21\listing21.1.exe). Jetzt weiß der Debugger, welche Anwendung mit dem geöffneten Quellcode verknüpft ist. Drücken Sie die Funktionstaste (F5), um die Anwendung zu starten, oder gehen Sie ins DEBUGGEN-Menü und klicken Sie auf STARTEN. Die Anwendung wird ganz normal angezeigt und der CLR-Debugger steht bereit. Ist jedoch die Anwendung bereits gestartet, gehen Sie in das Menü EXTRAS im CLRDebugger und wählen den Befehl DEBUGPROZESSE aus. Ein Listing wie in Abbildung 21.6 erscheint. Wählen Sie in der Liste Ihre Anwendung aus und klicken Sie auf ANFÜGEN. Daraufhin erscheint die Anwendung im unteren Teil des Dialogfeldes im Bereich GEDEBUGGTE PROZESSE in Abbildung 21.6. Klicken Sie auf die UNTERBRECHEN-Schaltfläche, um die Ausführung anzuhalten und den Code anzusehen. Das Anfügen an eine bereits laufende Anwendung ist sinnvoll, wenn man nicht über den entsprechenden Quellcode verfügt (wiewohl man nicht die Anwendung ohne den Quellcode ändern kann). Ein neues Fenster namens AUSGABE ist nun im CLR-Debugger verfügbar. Dieses Fenster zeigt, welche Module für die Anwendung geladen wurden. Das kann dazu beitragen, die Abhängigkeiten der Anwendung festzustellen.
691
Debugging und Profiling
Abbildung 21.6: Hängen Sie Ihrer Anwendung den Debugger durch einen Klick auf ANHÄNGEN an.
Wenn Sie sich mit einem Debugger in einen Prozess einklinken, sollten Sie sicherstellen, dass keine wichtige Anwendung läuft. Der Grund: Die Anwendung wird bei diesem Vorgang »eingefroren« und jede nicht gespeicherte Information kann verloren gehen. Ein Haltepunkt ist eine Stelle im Code, an der die Ausführung angehalten wird: Eine Pause tritt ein. Haltepunkte sind nützlich für die Fehlerbeseitigung in bestimmten Codezeilen. Wüssten Sie etwa, dass eine solche Zeile Fehler erzeugt, könnten Sie direkt davor einen Haltepunkt setzen und daraufhin den Debugger einsetzen, um die fraglichen Anweisungen und Variablen zu untersuchen. Um einen Haltepunkt zu setzen, klicken Sie auf die linke Spalte neben dem Quellcode Ihrer Datei. Ein roter Punkt sollte erscheinen, der anzeigt, dass die Ausführung in dieser Zeile stoppt. Halten Sie den Mauszeiger direkt über den Haltepunkt, um zu sehen, wo genau der Stopp passieren wird (vgl. Abbildung 21.7). Jetzt können Sie Ihre Anwendung wieder normal bedienen. Haben Sie keine Haltepunkte gesetzt und gibt es keine Fehlermeldungen, dürfte der Code so rasch ausgeführt werden, dass Sie nichts sehen können, wenn es passiert. Obwohl dies ein Hinweis ist, dass die Anwendung reibungslos funktioniert, hilft Ihnen das bei der Fehlerbeseitigung keineswegs. Um die Anwendung zu untersuchen, klicken Sie auf die DEBUGGEN BEENDEN-Schaltfläche (oder drücken (¢) + (F5)) und setzen am Anfang der OpenFile-Methode in Zeile 25 (Listing 21.1) einen Haltepunkt. Drücken Sie (F5) erneut.
692
JIT-Debugging
Abbildung 21.7: Wenn Sie in Ihrem Code einen Haltepunkt setzen, wird die Ausführung vorübergehend an der angegebenen Zeile gestoppt.
Die Anwendung startet ganz normal, doch sobald man auf die Schaltfläche klickt, hält die Anwendung an und der Debugger übernimmt die Kontrolle. Das Ausgabefenster rechts unten verändert sich, um die aktuell geladenen Dateien und Assemblies anzuzeigen. Dies verrät Ihnen alle Module, die von der Anwendung betroffen sind. Das LOKALFenster unten links (man muss eventuell auf die LOKAL-Registerkarte klicken, um es zu sehen) zeigt alle aktuellen Variablen sowie deren Werte an. Verwenden Sie das LOKALFenster, um festzustellen, ob die Variablen richtig eingestellt sind. Wenn Sie z.B. nicht herausfinden können, warum die Titelzeile nicht richtig angezeigt wird, schauen Sie im LOKALFenster unter this (Me in VB .NET), System.Windows.Forms.Form, WindowText nach und prüfen dort den eingetragenen Wert. Widerspricht er Ihren Erwartungen, wissen Sie, dass etwas vor diesem Haltepunkt schief gelaufen ist. Im LOKALFenster kann man sogar die Werte ändern, obwohl diese Änderungen nicht dauerhaft sind; man verliert sie, sobald die Fehlerbeseitigung beendet ist. Diese Information kann jedenfalls bei der Problemlösung hilfreich sein. Der CLR-Debugger lässt sich jedoch nicht als Editor missbrauchen: Man kann damit keinen Quellcode ändern. Zu diesem Zweck müssen Sie den WindowsEditor (NotePad) oder einen anderen bevorzugten Texteditor einsetzen, z.B. in Visual Studio .NET. Hat die Anwendung bei einem Haltepunkt angehalten, öffnen Sie das BEFEHLSFenster (unten rechts, oder wählen Sie FENSTER, DIREKT aus dem DEBUGGEN-Menü). In diesem Fenster können Sie Befehle Ihrer Anwendung ausführen, als ob sie sich im Quellcode
693
Debugging und Profiling
befänden. Tippen Sie in das BEFEHLSFenster MessageBox.Show("Gruss aus dem Befehlsfenster") und drücken Sie (¢). Der Debugger führt den Befehl aus und zeigt das Meldungsfeld an. Von diesem Fenster kann man praktisch jeden Befehl ausführen lassen. Als Nächstes öffnen Sie das ÜBERWACHEN-Fenster (aus dem DEBUGGEN/FENSTER-Menü während des Debugprozesses) und geben den Namen einer Variablen ein, die in Ihrer Seite benutzt wird (etwa rtbText), dann drücken Sie (¢). Nun können Sie die Variable beobachten, während Sie sich durch Ihre Anwendung bewegen, und sehen, ob ihr stets ein richtiger Wert zugewiesen wird. Tippen Sie Me in das ÜBERWACHEN-Fenster ein, können Sie alle Variablen sehen, die zu Me gehören. Diese beiden Fenster eignen sich gut dazu, jederzeit Informationen über die Anwendung zu liefern. Doch manchmal muss man die Applikation in Aktion sehen und nicht im »eingefrorenen« Zustand. Am oberen Rand des CLR-Debugger-Fensters befinden sich Schaltflächen, mit denen Sie die Ausführung der Anwendung in Echtzeit steuern können, so etwa das Springen zur nächsten Codezeile und das vollständige Beenden der Fehlerbeseitigung. Neben dem gelben Pfeil NÄCHSTE ANWEISUNG ANZEIGEN befinden sich drei Schaltflächen: EINZELSCHRITT, PROZEDURSCHRITT und AUSFÜHREN BIS RÜCKSPRUNG. Damit können Sie durch die Ausführung der Anwendung navigieren. Haben Sie etwa einen Haltepunkt auf eine Methode gesetzt, hält ein Klick auf PROZEDURSCHRITT die Ausführung bei jedem Statement in der Methode an. Das ist hilfreich, wenn man jede auszuführende Zeile sehen muss, doch es könnte etwas lästig werden, vor allem, wenn man so durch sehr viel Code »waten« muss. Wenn Sie auf einen Methodenaufruf stoßen, schickt der Debugger Sie nicht etwa zur entsprechenden Methode, sondern springt darüber. EINZELSCHRITT gleicht PROZEDURSCHRITT ein wenig, nur dass man in verschiedene Richtungen im Code verzweigen kann (etwa in andere Methoden), wenn es die Codelogik erfordert. Ein Klick auf die AUSFÜHREN BIS RÜCKSPRUNG -Schaltfläche, und die Anwendung führt die aktuelle Methode ohne anzuhalten aus, bis sie zum Beginn der nächsten Methode gelangt. Mit AUSFÜHREN BIS RÜCKSPRUNG können Sie also ganze Methoden überspringen, wenn Sie sicher wissen, dass sie in Ordnung sind. Zur Linken der SCHRITT-Schaltflächen befinden sich Steuerelemente, die wie die Steuertasten eine Rekorders aussehen: WEITER, ALLE UNTERBRECHEN, DEBUGGEN BEENDEN und NEU STARTEN. Diese Schaltflächen tun genau das, was auf ihnen steht, so dass man damit den Debug-Vorgang als Ganzes steuern kann.
Das Werkzeug CorDbg CorDbg.exe ist praktisch die Befehlszeilenversion des JIT-Debuggers DbgClr.exe. Er funkti-
oniert auf die gleiche Weise, doch statt eine grafische Benutzeroberfläche zu bedienen, muss man Befehle über die Eingabeaufforderung erteilen. Die Bedienung ist daher ein wenig unkomfortabler und das Tool wird hier nicht eingehend besprochen.
694
JIT-Debugging
Um das Programm CorDbg.exe zu starten, ohne zuerst eine Anwendung zu laden, geben Sie lediglich CorDbg in der Befehlszeile ein. Dann erteilen Sie den Befehl run anwendung name
Stellen Sie sicher, dass sich die zu prüfende Anwendung im selben Verzeichnis befindet, aus dem heraus Sie den Befehl erteilt haben. Verwendet man listing21.1.exe, sollte man etwas wie in Abbildung 21.8 zu sehen bekommen.
Abbildung 21.8: CorDbg ist ein befehlszeilengestützter Debugger, mit dem Sie der Ursache eines Problems auf den Grund gehen können.
Die Anwendung wird nun an der Main-Methode gestartet und wieder angehalten. Um zur nächsten Zeile der Ausführung zu gelangen, tippt man den Befehl si ein. So kann man sich zeilenweise durch die Anwendung bewegen, was etwas nervend ist. Ausführungszeilen sind mitunter etwas anderes als Codezeilen. Um sich zeilenweise durch Code zu bewegen, gibt man den Befehl next ein. Wenn man sich durch die Anwendung bewegt, sieht man zuweilen merkwürdige Bildschirmausgaben wie diese: (cordbg) next [0044] call (cordbg) next
dword ptr ds:[02FF17B8h]
[004a] mov (cordbg) next
ecx,ebx
[004c] call
dword ptr ds:[02FF2C18h]
Diese Befehle verraten Ihnen, was im Speicher vor sich geht, wenn die Anwendung läuft. dword ptr ist z.B. eine spezielle Anweisung, die auf eine Stelle im Arbeitsspeicher zeigt. Meistens hat Sie diese Information nicht zu interessieren, doch wenn Sie zu Ausführungsprozessen auf niederer Ebene hinuntergehen wollen, so erhalten Sie hierdurch Analysedaten. Informationen über die Befehle in CorDbg.exe finden Sie in der Dokumentation zum .NET Framework.
695
Debugging und Profiling
21.3 Profiling Ihrer Anwendung Profiling ist der Vorgang, bei dem eine Anwendung beobachtet und ihre verwendeten Ressourcen untersucht werden. Dieses Vorgehen ist sinnvoll, wenn man die Leistung einer Anwendung optimieren muss, weil man hier bemerkt, wo Engpässe auftreten und welche Codeabschnitte relativ lange brauchen, bis sie ausgeführt sind. Auf Grund dieser Informationen kann man seinen Quellcode neu strukturieren oder umschreiben, um mehr Leistung zu erzielen – eine Änderung, die stets willkommen sein dürfte. Leider steht für diese Aufgabe kein Werkzeug zur Verfügung. Doch dank der ProcessKlasse können Sie immerhin Ihr eigenes erstellen. Ein Prozess stellt auf Ihrem Computer eine ausgeführte Anwendung dar. Für die Prozesse sind Threads tätig und Prozesse werden im Windows Task-Manager angezeigt (vgl. Abbildung 21.9).
Abbildung 21.9: Der Task-Manager von Windows zeigt alle aktuell gestarteten Prozesse.
Die Process-Klasse lässt sich dazu einsetzen, jeden gerade ausgeführten Prozess zu untersuchen. Sie liefert Informationen wie etwa über die Speichermenge, die eine Anwendung beansprucht, ihren Anteil an Prozessorzeit und die Start- und Schlusszeiten einer bestimmten Methode. Somit sehen Sie nicht nur, wie die Leistung der Anwendung aussieht, sondern auch, wie sie gegenüber anderen Prozessen auf dem Computer abschneidet.
696
Profiling Ihrer Anwendung
Sehen Sie sich erst einmal eine Beispielanwendung an. Listing 21.2 verwendet die Process-Klasse zur Anzeige von statistischen Angaben über die aktuelle Anwendung. Listing 21.2: Prozesse überwachen 1: using System; 2: using System.Windows.Forms; 3: using System.Drawing; 4: 5: using System.Diagnostics; 6: 7: namespace TYWinforms.Day21 { 8: public class Listing212 : Form { 9: private Label lblProcessor = new Label(); 10: private Label lblMemory = new Label(); 11: private Label lblTotProcessor = new Label(); 12: private Process objProcess = new Process(); 13: private System.Windows.Forms.Timer tmrProcess = new System.Windows.Forms.Timer(); 14: 15: public Listing212() { 16: lblProcessor.Size = new Size(250,20); 17: lblProcessor.Location = new Point(10,10); 18: 19: lblMemory.Size = new Size(250,20); 20: lblMemory.Location = new Point(10,30); 21: 22: lblTotProcessor.Size = new Size(250,20); 23: lblTotProcessor.Location = new Point(10,50); 24: 25: tmrProcess.Tick += new EventHandler(this.Update); 26: tmrProcess.Interval = 500; 27: tmrProcess.Start(); 28: 29: objProcess = Process.GetCurrentProcess(); 30: 31: this.Text = ".NET Profiler"; 32: this.Controls.Add(lblProcessor); 33: this.Controls.Add(lblMemory); 34: this.Controls.Add(lblTotProcessor); 35: } 36: 37: private void Update(Object Sender, EventArgs e) { 38: objProcess.Refresh();
697
Debugging und Profiling
39: lblMemory.Text = "Speicher: " + objProcess.PrivateMemorySize.ToString(); 40: lblProcessor.Text = "Private Prozessorzeit: " + objProcess.PrivilegedProcessorTime.ToString(); 41: lblTotProcessor.Text = "Gesamte Prozessorzeit: " + objProcess.TotalProcessorTime.ToString(); 42: } 43: 44: public static void Main() { 45: Application.Run(new Listing212()); 46: } 47: } 48: }
Zunächst sind wie immer die richtigen Namensräume zu importieren. In Zeile 5 importiert man den Namensraum System.Diagnostics, der die Process-Klasse enthält. In den Zeilen 9 bis 13 erstellen Sie ein paar Steuerelemente, darunter ein Process-Objekt und einen Timer. Das Objekt beobachtet noch keine Prozesse, sondern ist noch untätig. Das wird sich in Zeile 29 ändern. Im Konstruktor in den Zeilen 16 bis 23 initialisieren Sie die verschiedenen Label-Steuerelemente, die erstellt wurden. Jedes Bezeichnungsfeld zeigt einen Aspekt des zu beobachtenden Prozesses an. Der Timer ist so eingestellt, dass er jede halbe Sekunde ausgelöst wird (Zeile 26), damit Sie so die Label-Steuerele-
mente aktualisieren können; sonst würden Sie keine zutreffende Statistik erhalten. Der Tick-Ereignishandler ist die Update-Methode in Zeile 37 (gleich mehr dazu). In Zeile 29 rufen Sie die Methode GetCurrentProcess der Process-Klasse auf. Diese Zeile veranlasst das neue Process-Objekt dazu, den Prozess, der die Anwendung darstellt, zu überwachen. Sie betrachten also die Statistik der mit Listing 21.2 erstellten Anwendung. Obwohl das Beobachten des Prozesses der aktuellen Anwendung angenehm ist, so ist es doch nicht immer möglich oder sinnvoll. Man kann eine Reihe anderer Methoden der Process-Klasse benutzen, um Prozesse anderer Anwendungen zu überwachen. GetProcessById etwa beschafft einen Prozess anhand der Identifikation im Betriebssystem. (Wenn Sie die Prozess-ID [PID] nicht im Task-Manager sehen, klicken Sie auf das ANSICHT-Menü, wählen SPALTEN AUSWÄHLEN und im Auswahlfenster PID (PROZESS-ID).) Diese Methode eignet sich gut, wenn man die PID des zu beobachtenden Prozesses kennt, aber das ist nicht immer der Fall. GetProcesses liefert alle gerade im System ablaufenden Prozesse, so dass man mehrere Anwendungen zugleich beobachten kann. GetProcessesByName schließlich liefert ein Array von Processes, die dem angegebenen
698
Profiling Ihrer Anwendung
Namen entsprechen. Dieser Name ist meist der der ausführbaren Datei der Anwendung, aber ohne die Erweiterung .exe. Würde man z.B. listing21.1 .exe ausführen, könnte man ihren Prozess mit folgendem Befehl beobachten: objProcess = Process.GetProcessesByName("listing21.1")[0];
Denken Sie daran, dass diese Methode ein Array liefert. Sie müssen also angeben, welches Element im Array Sie beobachten wollen. Meist ist im Array nur ein Element vorhanden; gibt man also [0] an (oder (0) in VB .NET), dann funktioniert das gut. Der Prozess muss gerade ablaufen, sonst erhalten Sie eine Fehlermeldung (eine gute Stelle für einen try-catch-Block). Springen Sie nun nach unten zur Update-Methode. Wenn man anfangs eine der Eigenschaften der Process-Klasse anzeigt, dann holt man einen Schnappschuss dieser Eigenschaft. Anders ausgedrückt: Die Eigenschaften aktualisieren sich nicht selbst. Daher muss man die Process.Refresh-Methode aufrufen, um aktualisierte Werte zu beschaffen, wie in Zeile 38 zu sehen. Die Zeilen 39 bis 41 schließlich zeigen diverse Eigenschaften des aktuell ablaufenden Prozesses an. Abbildung 21.10 zeigt das Ergebnis, nachdem die Anwendung für einige Sekunden ausgeführt worden ist.
Abbildung 21.10: Sie können zusehen, wie die Ressourcenstatistik Ihrer Anwendung dynamisch aktualisiert wird.
Die Process-Klasse liefert etliche Informationen über eine bestimmte Anwendung, von der beanspruchten Prozessorzeit bis zur Titelzeile des aktuell geöffneten Fensters. Tabelle 21.2 führt alle nützlichen Eigenschaften von Process auf; es sind eine ganze Menge.
699
Debugging und Profiling
Eigenschaft
Beschreibung
BasePriority
Liefert die Priorität des aktuellen Prozesses (vgl. Tag 19).
ExitCode
Ein Integer, den eine Anwendung liefert, wenn sie beendet wird. Null bedeutet meist, dass das Schließen normal erfolgte; andere Werte können auf Fehler hinweisen.
ExitTime
Gibt die Zeit an, zu der der verknüpfte Prozess endete.
Handle
Liefert den Handle, der mit einem Prozess verknüpft ist.
HandleCount
Liefert die Anzahl der Handles, die mit einem Prozess verknüpft sind.
HasExited
Gibt an, ob der Prozess angehalten wurde.
Id
Gibt den Wert der Prozess-Identitätsnummer (PID) an.
MachineName
Gibt den Namen des Rechners an, auf dem der Prozess gerade läuft.
MainModule
Liefert ein ProcessModule-Objekt, das das Modul darstellt, welches die Anwendung startete (normalerweise eine .exe- oder .dll-Datei).
MainWindowHandle
Gibt den Handler für das Primärfenster des Prozesses an.
MainWindowTitle
Gibt die Titelzeile des Primärfensters des Prozesses an.
MaxWorkingSet
Gibt den maximalen Speicherplatz an, der für einen vorliegenden Prozess reserviert wird.
MinWorkingSet
Gibt den minimalen Speicherplatz an, der für einen vorliegenden Prozess reserviert wird: Das Speichervolumen, das eine Anwendung nutzt, sinkt nie unter diese Marke.
Modules
Liefert ein Array von ProcessModule-Objekten, die eine Anwendung geladen hat (z.B. .exe- und .dll-Dateien)
NonpagedSystemMemorySize Gibt den Speicherumfang an, der einem Prozess zugewiesen ist und
der nicht der Auslagerungsdatei zugeschlagen werden kann. PagedMemorySize
Gibt den Speicherumfang an, der einem Prozess zugewiesen ist und der der Auslagerungsdatei zugeschlagen werden darf.
PagedSystemMemorySize
Gibt die Menge des Systemspeichers an, die dem Prozess zugewiesen ist und die in die Auslagerungsdatei geschrieben werden kann.
PeakPagedMemorySize
Gibt die maximale Speichermenge an, die in die Auslagerungsdatei geschrieben wird, welche einem Prozess zugewiesen wurde.
Tabelle 21.2: Prozessbezogene Eigenschaften von Process
700
Profiling Ihrer Anwendung
Eigenschaft
Beschreibung
PeakVirtualMemorySize
Gibt die maximale Menge an virtuellem Speicher an, die ein Prozess verlangt hat.
PeakWorkingSet
Gibt die maximale Menge an virtuellem Speicher an, die ein Prozess zu einem Zeitpunkt verlangt hat.
PriorityBoostEnabled
Gibt an, ob dem Prozess vorübergehend eine höhere Priorität eingeräumt (und er so schneller ausgeführt) werden soll, wenn sein Hauptfenster den Fokus hat.
PriorityClass
Ein PriorityClass-Objekt, das den Prioritätsgrad darstellt, den ein Prozess besitzt wie etwa Normal oder High).
PrivateMemorySize
Gibt die Speichermenge an, die dem aktuellen Prozess zugewiesen ist, und die andere Anwendungen nicht teilen können.
PrivilegedProcessorTime
Gibt die für einen Codeverarbeitungsprozess im Betriebssystemkern verbrauchte Prozessorzeit an.
ProcessName
Der Name des aktuellen Prozesses.
ProcessorAffinity
Gibt die CPUs an, auf denen ein Prozess in einem Mehrprozessorsystem laufen kann.
Responding
Gibt an, ob die Benutzeroberfläche des aktuellen Prozesses auf Benutzereingaben reagiert (oder ob sich das Programm »aufgehängt« hat).
StandardError
Beschafft ein StreamReader-Objekt, um Fehlermeldungen der Anwendung zu lesen.
StandardInput
Holt ein StreamWriter-Objekt, das sich dazu verwenden lässt, dem Prozess Eingaben zu schicken.
StandardOutput
Holt ein StreamReader–Objekt, um Standardausgaben der Anwendung zu lesen.
StartInfo
Informationen, die mit dem Prozess verbunden sind, wenn er gestartet wird (so etwa der Name der ausführbaren Datei, die den Prozess startet).
StartTime
Der Zeitpunkt, zu dem der Prozess gestartet wurde.
Threads
Die Thread-Objekte, die mit dem aktuellen Prozess verknüpft sind.
TotalProcessorTime
Gibt die gesamte Zeit an, die die CPU für die Ausführung dieses Prozesses aufgewendet hat (also die Summe aus den Werten UserProcessorTime und PrivilegedProcessorTime).
Tabelle 21.2: Prozessbezogene Eigenschaften von Process (Forts.)
701
Debugging und Profiling
Eigenschaft
Beschreibung
UserProcessorTime
Gibt die gesamte Zeit an, die die CPU für die Ausführung von Code innerhalb der Anwendung, die von diesem Prozess repräsentiert wird, aufgewendet hat.
VirtualMemorySize
Gibt die aktuelle Größe des virtuellen Speichers des gegenwärtigen Prozesses an.
WorkingSet
Gibt an, wie viel Speicher der damit verbundene Prozess gerade beansprucht.
Tabelle 21.2: Prozessbezogene Eigenschaften von Process (Forts.)
Bei all den verschiedenen Eigenschaften ist es mitunter schwierig herauszufinden, welche man für das Profiling einsetzen soll. Das hängt im Grunde von der Art der zu erstellenden Anwendung ab. Handelt es sich etwa um ein Datenbankprogramm, will man sicher die verschiedenen speicherrelevanten Eigenschaften sehen. Ein hoher PrivateMemorySizeWert könnte bedeuten, dass man umfangreiche Datenbankinformationen im Speicher hält, was nicht unbedingt eine schlechte Idee ist. Ein hoher VirtualMemorySize-Wert bedeutet, dass sich eine große Informationsmenge, die sich im Speicher befinden sollte, noch auf der Festplatte (in der Auslagerungsdatei) befindet, was der Leistung abträglich ist. Vielleicht beschaffen Sie zu viele Daten aus der Datenbank und ein Teil davon wird auf die Festplatte ausgelagert. Eine mögliche Lösung besteht darin, nach und nach kleinere Datenmengen zu holen. Entwerfen Sie eine Anwendung mit komplizierten Berechnungen darin, so sollten Sie die verschiedenen Eigenschaften beobachten, die die Prozessorzeit betreffen. Eine 3D-CADAnwendung beispielsweise muss zahlreiche komplizierte Berechnungen der linearen Algebra ausführen, die Prozessorzeit fressen. Man kann die Eigenschaft UserProcessorTime überwachen, um herauszufinden, wie viel Prozessorzeit Ihre Berechnungen verschlingen. Außerdem lässt sich beobachten, welche Auswirkungen das Hinzufügen weiterer Threads auf die Leistung der Anwendung hat.
21.4 Zusammenfassung Debugging und Profiling sind zwei Schritte in der Anwendungsentwicklung, die leider häufig übergangen werden. Beide sind jedoch notwendig, um sicherzustellen, dass eine Anwendung stabil und effizient läuft. Mit Hilfe des .NET Frameworks lassen sich beide Schritte leicht realisieren.
702
Fragen und Antworten
Als Erstes haben Sie den try-catch-Block kennen gelernt. Damit können Sie Fehler behandeln, die nicht immer vorherzusehen sind. Solche Fehler können in Code vorkommen, der mit E/A-Operationen zu tun hat, oder in Code, der von bestimmten Benutzereingaben abhängt. Man kann schließlich nie vorhersagen, was ein Benutzer tun wird. Als Just-In-Time- (JIT-) Debugging wird der Vorgang bezeichnet, bei dem einer laufenden Anwendung ein Debugger angehängt wird, damit Sie den Ablauf ihrer Ausführung beobachten können. Der CLR-Debugger und CorDbg sind zwei JIT-Debugger, mit denen man alles sehen kann, was in einer Anwendung passiert, von Codeausführung bis hin zu Verweisen auf Speicherstellen. Profiling schließlich ist der Vorgang, bei dem die Ressourcen, die eine Anwendung bei der Ausführung beansprucht, beobachtet werden. Wenn man den Speicherbedarf und den Anteil an Prozessorzeit betrachtet, kann man herausfinden, ob und wie eine Anwendung optimiert werden sollte. Sie haben das Ende Ihrer 21 Tage erreicht. Ich hoffe, die Reise durch Windows Forms hat Ihnen gefallen. Vergessen Sie nicht, sich das letzte Bonusprojekt anzusehen, um alles, was Sie in den letzten drei Wochen gelernt haben, noch einmal Revue passieren zu lassen.
21.5 Fragen und Antworten F
Kann ich den Windows-Systemmonitor (perfmon.exe) einsetzen, um meine Anwendungen zu untersuchen? A
Natürlich. Öffnen Sie den Systemmonitor (z.B. über AUSFÜHREN im STARTMenü, wo Sie den Dateinamen eingeben) und klicken Sie im SystemmonitorFenster auf die Plus-Schaltfläche (HINZUFÜGEN) in der Symbolleiste, um dem Fenster Informationen über eine gerade ablaufende Anwendung hinzuzufügen. Bei der Option DATENOBJEKT wählen Sie .NET CLR SPEICHER. Der Befehl LEISTUNGSINDIKATOREN WÄHLEN über dem Listenfeld führt verschiedene Aspekte auf, die man beobachten kann. Im Feld INSTANZ WÄHLEN des Listenfeldes rechts daneben sollten Sie Ihre Anwendung sehen (neben mmc und _Global_), z.B. listing21.1.exe. Markieren Sie sie und klicken Sie auf HINZUFÜGEN, um diesen Aspekt der Anwendung zu überwachen. Sollte Ihnen dieses Standardverhalten nicht genügend Informationen liefern, so können Sie einen benutzerdefinierten Systemmonitor mit der PerformanceCounter-Klasse erstellen. Mit dieser Klasse lassen sich Aspekte einer Anwendung definieren, die man verfolgen will und die als Leistungsindikatoren bezeichnet werden.
703
Debugging und Profiling
21.6 Workshop Dieser Workshop soll Ihnen helfen, sich besser an die heute behandelten Begriffe zu erinnern. Es hilft Ihnen sehr, die Antworten vollständig zu verstehen, bevor Sie weiterlesen. Die Antworten zu den Quizfragen und den Übungen finden Sie in Anhang A.
Quiz 1. Wahr oder falsch? Ein catch-Block, der eine Ausnahme vom Typ IOException angibt, wird auch Ausnahmen vom Typ SystemException abfangen. 2. Ist der folgende Code richtig? Process [] objProcess = Process.GetProcessesByName ("listing21.2.exe");
3. Wie erzeugt man eine Programmdatenbankdatei? 4. Was versteht man unter einem Haltepunkt? 5. Nennen Sie drei Eigenschaften der Exception-Klasse. 6. Wie kann man eigene Fehlermeldungen auslösen?
Übung Schreiben Sie eine Anwendung, die dem Windows Task-Manager gleicht. Sie sollte den Benutzer jeden ablaufenden Prozess über ein Listenfeld auswählen lassen. In Bezeichnungsfeldern sollten die Eigenschaften Id, MainModule, PrivateMemorySize und TotalProcessorTime des Prozesses sowie die Zahl der aktiven Threads zu sehen sein. Denken Sie auch daran, try-catch-Blöcke einzusetzen!
704
Woche 3 – Rückblick Projekt 3: Das Textprogramm vervollständigen In diesem letzten Wochenrückblick bauen Sie auf die in den letzten zwei Wochenrückblicken erstellte NetWord-Anwendung auf. Beim letzten Mal machten Sie daraus eine MDIApplikation und legten eine Document-Klasse an, die jedes untergeordnete Dokument im übergeordneten Formular darstellt. Diesmal gestalten Sie den Funktionsumfang noch Wordähnlicher, indem Sie ein benutzerdefiniertes Windows Forms-Steuerelement erzeugen.
Eine Klasse für eine neue Seite erzeugen Zunächst werfen wir einen Blick auf den vorhandenen Code. Da haben wir die Datei NetWord.cs, die unverändert bleiben soll. Sie ist der Haupteinstiegspunkt für die Anwendung. Des Weiteren findet sich die Datei FindDialog.cs, die ein benutzerdefiniertes DialogfeldSteuerelement enthält, mit dem der Benutzer im Dokument nach Text suchen kann. Schließlich liegt noch die Datei Document.cs vor, die den Großteil der Benutzeroberfläche enthält, ein RichTextBox-Steuerelement, in das der Benutzer sein Dokument eingibt. Diese Klasse verfügt zudem über Menüs, um Standardfunktionen bereitzustellen, und nutzt die FindDialog-Klasse für Suchfunktionen. Soweit arbeitet die Anwendung recht ähnlich wie der Windows-Editor. Sie präsentiert einen einzelnen Dateneingabepunkt, der einfach größer wird, je mehr Text der Benutzer eingibt. Dieses Verhalten steht aber im Gegensatz zu Microsoft Word, in dem Dokumente durch tatsächliche Seiten dargestellt werden. In Word kann man seinen Text in logische Einheiten aufteilen und bündeln, die sich manipulieren lassen: zum Drucken, Durchsuchen, Setzen von Seitenrändern usw. Durch das Anlegen einer neuen Page-Klasse bauen wir eine ähnliche Funktionalität ein, um so auch die vorhandene Dokumentenschnittstelle zu ersetzen. Diese Klasse erweitert das RichTextBox-Steuerelement und stellt Funktionalität bereit, die nicht in diesem Steuerelement mitgeliefert wird. Listing R3.1 zeigt den Code für die neue Page-Klasse.
705
Woche 3 – Rückblick
Listing R3.1: Die Page-Klasse 1: using System; 2: using System.Drawing; 3: using System.Windows.Forms; 4: using System.Drawing.Printing; 5: 6: namespace TYWinforms.P3 { 7: public class Page : RichTextBox { 8: public int PageNumber; 9: private int intCharIndex; 10: private int intLineIndex; 11: public event EventHandler Filled; 12: 13: private PageSettings objPageSettings; 14: 15: public int CharIndex { 16: get { return intCharIndex; } 17: } 18: 19: public int LineIndex { 20: get { return intLineIndex; } 21: } 22: 23: public Page(Form frmParent, PageSettings objPageSettings, int intPageNumber) { 24: intCharIndex = 1; 25: intLineIndex = 1; 26: PageNumber = intPageNumber; 27: int intWidth = (int)(.96 * (objPageSettings.Bounds.Width (objPageSettings.Margins.Left + objPageSettings.Margins.Right))); 28: int intHeight = (int)(.96 * (objPageSettings.Bounds.Height (objPageSettings.Margins.Top + objPageSettings.Margins.Bottom))); 29: 30: this.Parent = frmParent; 31: this.ScrollBars = RichTextBoxScrollBars.None; 32: this.Font = frmParent.Font; 33: this.objPageSettings = objPageSettings; 34: this.RightMargin = objPageSettings.Bounds.Width - (int)(.96 * (objPageSettings.Margins.Left + objPageSettings.Margins.Right)); 35: this.Size = new Size(intWidth,intHeight); 36: } 37: 38: public void SetFont(Font fnt, Color clr) { 39: this.SelectionFont = fnt; 40: this.SelectionColor = clr;
706
Woche 3 – Rückblick
41: } 42: 43: protected virtual void OnFilled(EventArgs e) { 44: Filled(this, e); 45: } 46: 47: public void UpdatePageSettings(PageSettings objPageSettings) { 48: this.objPageSettings = objPageSettings; 49: 50: this.RightMargin = objPageSettings.Bounds.Width - (int)(.96 * (objPageSettings.Margins.Left + objPageSettings.Margins.Right)); 51: } 52: 53: protected override void OnSelectionChanged(EventArgs e) { 54: intLineIndex = this.GetLineFromCharIndex (this.SelectionStart) + 1; 55: 56: int i = this.SelectionStart; 57: while (this.GetPositionFromCharIndex(i).X > 1) { 58: --i; 59: } 60: 61: intCharIndex = SelectionStart - i + 1; 62: 63: if (this.GetPositionFromCharIndex (this.SelectionStart).Y + (this.SelectionFont.GetHeight()*2) >= this.ClientSize.Height) { 64: OnFilled(new EventArgs()); 65: } 66: 67: base.OnSelectionChanged(e); 68: } 69: } 70: }
In Zeile 7 sehen Sie die Deklaration der Page-Klasse, die von der RichTextBoxKlasse abgeleitet ist. Dadurch können wir (vgl. Tag 18) ein neues Steuerelement erstellen, das auf dem abgeleiteten fußt. Das ist ideal, denn wir wollen das Steuerelement nicht von Grund auf neu erstellen. Die Zeilen 8 bis 21 zeigen diverse Eigenschaften, die in der Anwendung eingesetzt werden. In Zeile 11 erstellen wir ein benutzerdefiniertes Ereignis namens Filled. Es wird immer dann ausgelöst, wenn die Grenzen einer Seite überschritten werden (entweder indem man zuviel Text eingibt oder indem man einen Bildlauf durch die Seite ausführt). Das umgebende Steuerelement kann mit diesem Ereignis herausfinden, ob eine neue Seite hinzuzufügen ist.
707
Woche 3 – Rückblick
Wir wollen die Eigenschaften CharIndex und LineIndex (in den Zeilen 15 und 19) zur Verfügung stellen, damit das umgebende Steuerelement (welches die Document-Klasse wird) auf diese Variablen zugreifen kann. Wie wichtig diese Variablen sind, werden Sie später noch sehen. Beachten Sie die Syntax der Eigenschaftendeklaration. Der Konstruktor übernimmt ab Zeile 23 ein paar Eigenschaften. Die erste, frmParent, stellt das umgebende Steuerelement dar. objPageSettings repräsentiert die Einstellungen, die durch das Containersteuerelement festgelegt wurden (z.B. Ränder, Papiergröße und Seitenausrichtung). Die letzte Eigenschaft namens intPageNumber stellt die Nummer der Seite im umgebenden Steuerelement dar. Wenn die Anwendung mehr als eine Seite enthält, lässt sich mit dieser Eigenschaft die Nummer der aktuellen Seite herausfinden. Die Klasse, die eine Instanz dieser Page-Klasse anlegt, muss beim Erstellen neuer Seiten alle drei dieser Eigenschaften liefern. Die Zeilen 24 bis 35 instantiieren verschiedene Eigenschaften des Steuerelements. Besonders interessant ist dabei die RightMargin-Eigenschaft in Zeile 34. RightMargin ist eine Eigenschaft der RichTextBox-Klasse, die festlegt, wie viel Text in eine Zeile passt – d.h. der rechte Rand des Textfensters . Dies wird dadurch bestimmt, dass man die objPageSettings-Variable untersucht, die übergeben wurde; man subtrahiert die Ränder von der Gesamtseitenbreite, um den richtigen Wert zu erhalten. Die SetFont-Methode in Zeile 38 wird vom umgebenden Objekt benutzt, um die in der neuen Page-Klasse verwendeten Schriftarten festzulegen. Sie nehmen sie in Gebrauch, wenn wir die überarbeitete Document-Klasse untersuchen. In Zeile 43 wird die OnFilled-Methode erstellt, die lediglich das erwähnte FilledEreignis ausgelöst. Die UpdatePageSettings-Methode in Zeile 47 sollte jedes Mal ausgeführt werden, wenn sich die Seiteneinstellungen geändert haben. Da diese nicht in der Page-Klasse enthalten sind (sondern in der Document-Klasse), muss diese Methode mit dem neuen Seiteneinstellungsobjekt aufgerufen werden. Das RightMargin-Mitglied wird durch die neuen Einstellungen auf ähnliche Weise aktualisiert, wie es im Konstruktor initialisiert wurde. OnSelectionChanged in Zeile 53 ist die letzte Methode in der Page-Klasse. Diese Funktion befand sich in der Document-Klasse, wurde aber hierher verschoben, um einen logischeren Ablauf zu ermöglichen. Im Wesentlichen bringt die Methode die Werte intLineIndex und intCharIndex auf den jeweils aktuellen Stand im Steuerelement. Das Containersteuerelement kann daraufhin die
708
Woche 3 – Rückblick
öffentlichen Eigenschaften LineIndex und CharIndex nutzen, um die aktuelle Cursorposition anzuzeigen. Der einzige Unterschied in dieser Methode ist das Stück in den Zeilen 63 bis 65. In der Page-Klasse brauchen wir eine Möglichkeit, das umgebende Objekt wissen zu lassen, wann eine Seite voll ist (jedes Page-Objekt repräsentiert ja eine bedruckte Seite). Dies erledigt die Prüfung in Zeile 63; sie verwendet die Methode GetPositionFromCharIndex, um die Y-Koordinate des aktuellen Zeichens festzustellen. Ist dieser Wert ausreichend hoch (im Vergleich zur Höhe des Steuerelements), bedeutet dies, dass das Steuerelement voll ist und eine neue Seite angelegt werden sollte (beachten Sie die Berücksichtigung der Schrifthöhe bei der Y-Koordinate in Zeile 63; der Grund dafür ist der, dass die YKoordinate die Position am oberen Rand des Textes liefert, wir aber den unteren Rand benötigen, um herauszufinden, ob bereits der untere Rand des Steuerelements erreicht ist). Die OnFilled-Methode löst unser benutzerdefiniertes Filled-Ereignis aus, um dem umgebenden Steuerelement mitzuteilen, dass die Seite voll ist. Speichern Sie dieses Listing als Page.cs und kompilieren Sie es mit dem folgenden Befehl: csc /t:library /r:system.dll /r:system.windows.forms.dll /r:system.drawing.dll Page.cs
Nun sind Sie bereit, die Document-Klasse so zu verändern, dass statt der normalen RichTextBox- die neue Page-Klasse eingesetzt wird. Listing R3.2 zeigt den Code für Document.cs in seiner Gesamtheit. Das Listing ist umfangreich, doch der Großteil des Codes hat sich nicht geändert, so dass wir es nicht vollständig untersuchen. Nähere Details finden Sie im Wochenrückblick 2. Listing R3.2: Die revidierte Document-Klasse 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14:
using using using using using using using
System; System.Windows.Forms; System.IO; System.Drawing; System.Drawing.Printing; System.ComponentModel; System.Collections;
namespace TYWinforms.P3 { public class Document : Form { public ArrayList arrPages = new ArrayList(); public int intCurrentPage; private Panel pnlView = new Panel();
709
Woche 3 – Rückblick
15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60:
710
private private private private
ContextMenu cmnuDocument = new ContextMenu(); StatusBar sbarMain = new StatusBar(); StatusBarPanel spnlLine = new StatusBarPanel(); VScrollBar scrlbar = new VScrollBar();
private MainMenu mnuMain = new MainMenu(); private private private private private private private private private private private private private
MenuItem MenuItem MenuItem MenuItem MenuItem MenuItem MenuItem MenuItem MenuItem MenuItem MenuItem MenuItem MenuItem
mniFile = new MenuItem("File"); mniSave = new MenuItem("Save"); mniPageSetup = new MenuItem ("Page Setup..."); mniPrintPreview = new MenuItem ("Print Preview"); mniPrint = new MenuItem("Print..."); mniEdit = new MenuItem("Edit"); mniUndo = new MenuItem("Undo"); mniCut = new MenuItem("Cut"); mniCopy = new MenuItem("Copy"); mniPaste = new MenuItem("Paste"); mniFind = new MenuItem("Find..."); mniFormat = new MenuItem("Format"); mniFont = new MenuItem("Font...");
private private private private
Font fntCurrent = new Font ("Times New Roman", 10); Color fntColor = Color.Black; FontDialog dlgFont = new FontDialog(); PageSetupDialog dlgPageSetup = new PageSetupDialog();
private PageSettings objPageSettings = new PageSettings(); private PrinterSettings objPrintSettings = new PrinterSettings(); private StringReader objReader; public Document(string strName) { mniSave.Click += new EventHandler(this.FileClicked); mniPageSetup.Click += new EventHandler(this.FileClicked); mniPrintPreview.Click += new EventHandler(this.FileClicked); mniPrint.Click += new EventHandler(this.FileClicked); mnuMain.MenuItems.Add(mniFile); mniFile.MergeType = MenuMerge.MergeItems; mniFile.MenuItems.Add(mniSave); mniFile.MenuItems.Add("-"); mniFile.MenuItems.Add(mniPageSetup); mniFile.MenuItems.Add(mniPrintPreview); mniFile.MenuItems.Add(mniPrint); mniFile.MergeOrder = 1;
Woche 3 – Rückblick
61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80: 81: 82: 83: 84: 85: 86: 87: 88: 89: 90: 91: 92: 93: 94: 95: 96: 97: 98: 99: 100: 101: 102: 103: 104: 105: 106:
mniEdit.MergeOrder = 2; mniFormat.MergeOrder = 3; mniSave.MergeOrder = 30; mniFile.MenuItems[1].MergeOrder = 35; mniPageSetup.MergeOrder = 40; mniPrintPreview.MergeOrder = 45; mniPrint.MergeOrder = 50; sbarMain.ShowPanels = true; sbarMain.Font = new Font("Arial", 10); sbarMain.Panels.Add(spnlLine); spnlLine.AutoSize = StatusBarPanelAutoSize.Spring; spnlLine.Alignment = HorizontalAlignment.Right; mniUndo.Click += new EventHandler(this.EditClicked); mniCut.Click += new EventHandler(this.EditClicked); mniCopy.Click += new EventHandler(this.EditClicked); mniPaste.Click += new EventHandler(this.EditClicked); mniFind.Click += new EventHandler(this.EditClicked); mniFont.Click += new EventHandler(this.FormatClicked); mnuMain.MenuItems.Add(mniEdit); mnuMain.MenuItems.Add(mniFormat); mniUndo.ShowShortcut = true; mniCut.ShowShortcut = true; mniCopy.ShowShortcut = true; mniPaste.ShowShortcut = true; mniFind.ShowShortcut = true; mniEdit.MenuItems.Add(mniUndo); mniEdit.MenuItems.Add("-"); mniEdit.MenuItems.Add(mniCut); mniEdit.MenuItems.Add(mniCopy); mniEdit.MenuItems.Add(mniPaste); mniEdit.MenuItems.Add("-"); mniEdit.MenuItems.Add(mniFind); mniFormat.MenuItems.Add(mniFont); cmnuDocument.MenuItems.Add(mniCut.CloneMenu()); cmnuDocument.MenuItems.Add(mniCopy.CloneMenu()); cmnuDocument.MenuItems.Add(mniPaste.CloneMenu()); cmnuDocument.MenuItems.Add("-"); cmnuDocument.MenuItems.Add(mniFont.CloneMenu());
711
Woche 3 – Rückblick
107: cmnuDocument.Popup += new EventHandler (this.HandleContext); 108: pnlView.ContextMenu = cmnuDocument; 109: 110: scrlbar.Dock = DockStyle.Right; 111: scrlbar.Scroll += new ScrollEventHandler (this.ScrollView); 112: scrlbar.Maximum = 10; 113: 114: this.Text = strName; 115: this.Font = new Font("Courier New", 12); 116: this.Size = new Size(800, 900); 117: this.Name = "NetWord"; 118: this.WindowState = FormWindowState.Maximized; 119: this.Menu = mnuMain; 120: this.Closing += new CancelEventHandler (this.DocumentClosing); 121: 122: pnlView.AutoScroll = false; 123: pnlView.BackColor = Color.DarkGray; 124: pnlView.Width = this.Width - 16; 125: pnlView.Height = this.Height - 50; 126: pnlView.Location = new Point(0,0); 127: pnlView.Font = fntCurrent; 128: 129: this.Controls.Add(scrlbar); 130: this.Controls.Add(sbarMain); 131: this.Controls.Add(pnlView); 132: AddPage(); 133: } 134: 135: private void ScrollView(Object Sender, ScrollEventArgs e) { 136: pnlView.Top = 0 - (e.NewValue * 30); 137: } 138: 139: private void DocumentCreated(Object Sender, EventArgs e) { 140: AddPage(); 141: } 142: 143: private void AddPage() { 144: int intHeight; 145: 146: intHeight = objPageSettings.Bounds.Height - (int)(.96 * (objPageSettings.Margins.Top + objPageSettings.Margins.Bottom)); 147: 148: Page objPage = new Page(this,objPageSettings,arrPages.Count+1); 149: pnlView.Height = (arrPages.Count+1) * intHeight; 150: pnlView.Controls.Add(objPage); 151: scrlbar.Maximum = (30 * arrPages.Count+1) + 15;
712
Woche 3 – Rückblick
152: 153: objPage.Location = new Point(50,intHeight * arrPages.Count + 5); 154: objPage.Filled += new EventHandler (this.PageFilled); 155: objPage.Enter += new EventHandler (this.PageGotFocus); 156: objPage.SelectionChanged += new EventHandler (this.UpdateStatus); 157: 158: arrPages.Add(objPage); 159: objPage.Focus(); 160: } 161: 162: private void PageFilled(Object Sender, EventArgs e) { 163: if (intCurrentPage == arrPages.Count) { 164: AddPage(); 165: } else { 166: ((Page)arrPages[intCurrentPage]).Focus(); 167: } 168: } 169: 170: private void PageGotFocus(Object Sender, EventArgs e) { 171: intCurrentPage = ((Page)Sender).PageNumber; 172: } 173: 174: private void UpdateStatus(Object Sender, EventArgs e) { 175: Page tmpPage = (Page)arrPages[intCurrentPage-1]; 176: 177: spnlLine.Text = "Page " + intCurrentPage.ToString() + " Line " + tmpPage.LineIndex.ToString() + " Char " + tmpPage.CharIndex.ToString(); 178: } 179: 180: private void HandleContext(Object Sender, EventArgs e) { 181: if (((Page)arrPages[intCurrentPage-1]).SelectionLength == 0) { 182: cmnuDocument.MenuItems[0].Enabled = false; 183: cmnuDocument.MenuItems[1].Enabled = false; 184: } else { 185: cmnuDocument.MenuItems[0].Enabled = true; 186: cmnuDocument.MenuItems[1].Enabled = true; 187: } 188: } 189: 190: private void FileClicked(Object Sender, EventArgs e) { 191: MenuItem mniTemp = (MenuItem)Sender; 192: PrintDocument pd; 193: String strText; 194: 195: switch (mniTemp.Text) { 196: case "Save":
713
Woche 3 – Rückblick
197: FileInfo filTemp = new FileInfo(this.Text); 198: if (filTemp.Extension == ".rtf") { 199: ((Page)arrPages[intCurrentPage-1]).SaveFile(this.Text, RichTextBoxStreamType.RichText); 200: } else { 201: ((Page)arrPages[intCurrentPage-1]).SaveFile(this.Text, RichTextBoxStreamType.PlainText); 202: } 203: ((Page)arrPages[intCurrentPage-1]). Modified = false; 204: break; 205: case "Page Setup...": 206: dlgPageSetup.PageSettings = objPageSettings; 207: dlgPageSetup.ShowDialog(); 208: 209: foreach (Page tmpPage in arrPages) { 210: tmpPage.UpdatePageSettings (objPageSettings); 211: } 212: 213: break; 214: case "Print Preview": 215: strText = ""; 216: 217: foreach (Page tmpPage in arrPages) { 218: strText += tmpPage.Text; 219: } 220: 221: objReader = new StringReader(strText); 222: 223: pd = new PrintDocument(); 224: pd.DefaultPageSettings = objPageSettings; 225: pd.PrintPage += new PrintPageEventHandler (this.PrintIt); 226: 227: PrintPreviewDialog dlgPrintPreview = new PrintPreviewDialog(); 228: dlgPrintPreview.Document = pd; 229: 230: dlgPrintPreview.ShowDialog(); 231: break; 232: case "Print...": 233: PrintDialog dlgPrint = new PrintDialog(); 234: dlgPrint.PrinterSettings = new PrinterSettings(); 235: if (dlgPrint.ShowDialog() == DialogResult.OK) { 236: strText = ""; 237: 238: foreach (Page tmpPage in arrPages) { 239: strText += tmpPage.Text;
714
Woche 3 – Rückblick
240: } 241: 242: objReader = new StringReader(strText); 243: 244: pd = new PrintDocument(); 245: 246: pd.DefaultPageSettings = objPageSettings; 247: pd.PrintPage += new PrintPageEventHandler (this.PrintIt); 248: pd.Print(); 249: } 250: break; 251: case "Exit": 252: this.Close(); 253: break; 254: } 255: } 256: 257: private void PrintIt(Object Sender, PrintPageEventArgs e) { 258: Font fntPrint = this.Font; 259: int count = 0; 260: float yPos = 0; 261: float lpp = e.MarginBounds.Height / fntPrint.GetHeight(e.Graphics); 262: float fltTopMargin = e.MarginBounds.Top; 263: float fltLeftMargin = e.MarginBounds.Left; 264: String strLine = null; 265: 266: while (count < lpp && ((strLine = objReader.ReadLine()) != null)) { 267: yPos = fltTopMargin + (count * fntPrint.GetHeight(e.Graphics)); 268: 269: e.Graphics.DrawString(strLine, fntPrint, Brushes.Black, fltLeftMargin, yPos, new StringFormat()); 270: 271: count++; 272: } 273: 274: if (strLine != null) { 275: e.HasMorePages = true; 276: } else { 277: e.HasMorePages = false; 278: } 279: } 280: 281: private void EditClicked(Object Sender, EventArgs e) { 282: MenuItem mniTemp = (MenuItem)Sender;
715
Woche 3 – Rückblick
283: Page tmpPage = (Page)arrPages[intCurrentPage-1]; 284: 285: switch (mniTemp.Text) { 286: case "Undo": 287: tmpPage.Undo(); 288: break; 289: case "Cut": 290: if (tmpPage.SelectedRtf != "") { 291: tmpPage.Cut(); 292: } 293: break; 294: case "Copy": 295: if (tmpPage.SelectedRtf != "") { 296: tmpPage.Copy(); 297: } 298: break; 299: case "Paste": 300: tmpPage.Paste(); 301: break; 302: case "Find...": 303: FindDialog dlgFind = new FindDialog(this); 304: dlgFind.Show(); 305: break; 306: } 307: } 308: 309: private void FormatClicked(Object Sender, EventArgs e) { 310: MenuItem mniTemp = (MenuItem)Sender; 311: 312: switch (mniTemp.Text) { 313: case "Font...": 314: dlgFont.ShowColor = true; 315: dlgFont.Font = this.Font; 316: if (dlgFont.ShowDialog() == DialogResult.OK) { 317: fntCurrent = dlgFont.Font; 318: fntColor = dlgFont.Color; 319: this.Font = fntCurrent; 320: this.ForeColor = fntColor; 321: ((Page)arrPages[intCurrentPage - 1]).SetFont(fntCurrent, fntColor); 322: } 323: break; 324: } 325: } 326: 327: private void DocumentClosing(Object Sender, CancelEventArgs e) {
716
Woche 3 – Rückblick
328: if (((Page)arrPages[intCurrentPage-1]).Modified) { 329: DialogResult dr = MessageBox.Show("Do you want to save the changes?", this.Name, MessageBoxButtons.YesNoCancel); 330: 331: if (dr == DialogResult.Yes) { 332: FileClicked(mniSave, new EventArgs()); 333: } else if (dr == DialogResult.Cancel) { 334: e.Cancel = true; 335: } 336: } 337: } 338: } 339: }
Als Erstes ist festzustellen, dass alle Verweise auf das RichTextBox-Steuerelement getilgt sind. Statt dessen finden sich Referenzen auf unsere neue PageKlasse. Wir benötigen einen Mechanismus, um in diesem Dokument einzelne Seiten speichern zu können. Dies lässt sich mit einem ArrayList-Objekt durchführen, das in Zeile 11 deklariert wird (und der Namensraum des Objekts, System.Collections, wird in Zeile 7 importiert). Wenn wir ein neues Page-Objekt anlegen, fügen wir es diesem Array hinzu. Wenn wir auf die jeweils aktuelle Seite zugreifen müssen, benutzen wir das Array und die intCurrentPage-Variable (gleich mehr davon). Die nächste Änderung tritt in Zeile 14 auf, wo wir ein neues Panel-Steuerelement erzeugen. Dieses Paneel soll jede neue Seite, die erzeugt wird, aufnehmen. Seine Eigenschaften werden im Document-Konstruktor initialisiert. Zeile 18 erzeugt ein neues VScrollBar-Objekt, das für den Bildlauf benutzt wird. Das Panel-Steuerelement verfügt über eine AutoScroll-Eigenschaft, die uns Bildlaufleisten zur Verfügung stellt, doch sie hat auch Nachteile. Wenn man sich z.B. im Panel-Steuerelement von einer Seite zur nächsten bewegt und AutoScroll auf true gesetzt ist, ist der Bildlauf der Page-Objekte viel zu groß– dadurch wird es schwierig, den Überblick über die aktuelle Texteingabestelle zu behalten. Daher implementieren wir benutzerdefinierte Bildlaufleisten, die tun, was wir wollen. Die VScrollBar wird in den Zeilen 110 bis 112 initialisiert, und ihr Ereignishandler, ScrollView, befindet sich in Zeile 135. Diese Methode verschiebt einfach unser Panel-Steuerelement um einen skalierten Betrag. Der letzte Teil des Konstruktors ruft die AddPage-Methode (Zeile 132) auf. AddPage enthält einen Teil der wichtigsten neuen Funktionen. Sie erzeugt ein neues Page-Objekt (Zeile 148; beachten Sie bitte die Übergabe der nötigen
717
Woche 3 – Rückblick
Variablen). Zeile 153 legt die Platzierung einer neuen Seite im Panel-Steuerelement fest. Dazu zählt sie einfach die vorhandenen Page-Objekte und setzt dementsprechend die oberste Koordinate. In den Zeilen 154 bis 156 werden Ereignishandler den Ereignissen Filled, Enter und SelectionChanged zugewiesen. Wir fügen das eben angelegte Page-Objekt dem arrPages-Array und dem Panel-Steuerelement hinzu und verleihen ihm den Fokus. Die Größe des Panel-Steuerelements ist zu ändern, um die neue Seite aufzunehmen (Zeile 149), und auch die Maximum-Eigenschaft der VScrollBar wird angepasst (Zeile 151). Denken Sie an die Bildlaufleiste in Microsoft Word: Fügt man neue Seiten hinzu, schrumpft die Bildlaufleiste kontinuierlich (da der Maximalwert der Bildlaufleiste laufend wächst). Wird die Seitengrenze überschritten, so ist dies der Auslöser unseres benutzerdefinierten Ereignisses Filled. Dessen Ereignishandler PageFilled (Zeile 162) fügt entweder eine neue Seite hinzu (wenn es sich aktuell um die letzte Seite handelt) oder überträgt den Fokus auf die nächste Seite im Dokument. Beachten Sie die Verwendung der Variablen arrPages und intCurrentPage in Zeile 166. PageGotFocus, der Ereignishandler für das Enter-Ereignis, findet sich in Zeile 170. Er aktualisiert die Variable intCurrentPage. Der Ereignishandler für das SelectionChanged-Ereignis, UpdateStatus, wurde in Zeile 174 geschrieben. Damit können wir nun auf die Eigenschaft von Page in Zeile 177 zugreifen. Wieder ist auf den Einsatz der Variablen arrPages und intCurrentPage in Zeile
175 zu achten. Der übrige Code zwischen Zeile 180 und Zeile 339 hat sich nur geringfügig verändert. Alle Verweise auf das RichTextBox-Steuerelement wurden durch Referenzen auf ein PageObjekt ersetzt. Die Fälle »Print Preview« (Druckvorschau) und »Print« in den Zeilen 214 und 232 wurden geändert, um durch alle Page-Objekte im Dokument zu blättern, wobei sie den Text jeder Seite einsammeln, bevor sie ihn dem StringReader-Objekt in den Zeilen 221 und 242 übergeben. Die FormatClicked-Methode in Zeile 309 verweist nun auf die neue Page.SetFont-Methode, die wir in Listing P3.1 angelegt haben. Der Aufruf der FindDialog-Klasse in Zeile 303 hat sich leicht geändert, um eine neue Variable an den Konstruktor zu übergeben. (Dies wird in den nächsten Abschnitten erklärt.) Die FindDialog-Klasse ist so abzuändern, dass sie jedes Page-Objekt durchsucht statt nur ein einzelnes RichTextBox-Steuerelement wie zuvor. Um diese Funktionalität zu erreichen, sind zwei spezifische Änderungen notwendig: den Konstruktor von FindDialog so modifizieren, dass er auf die Document-Klasse statt auf sein RichTextBox-Steuerelement verweist, und die Methoden für Suchen, Ersetzen und Alle ersetzen so anpassen, dass sie jedes PageObjekt durchsuchen.
718
Woche 3 – Rückblick
Zunächst ändern Sie den Konstruktor wie folgt: private Document docParent; public FindDialog(Form frmDocument):base(){ docParent =(Document)frmDocument; ...
Nun nimmt die docParent-Variable eine Referenz auf unsere Haupt-Document-Klasse auf, welche wiederum ihrerseits einen Verweis auf alle Page-Objekte enthält. Listing R3.3 zeigt die Änderungen am ButtonClicked-Ereignishandler, der für die eigentliche Suchen- und Ersetzen-Funktionalität zuständig ist. Listing R3.3: Suchen und Ersetzen in Page-Objekten 1: 2: 3: 4: 5: 6: 7: 8:
private void ButtonClicked(Object Sender, EventArgs e) { Button btTemp = (Button)Sender; int intLocation; int intCount = 0;
switch (btTemp.Text) { case "Suchen": for (int i = docParent.intCurrentPage-1; i < docParent.arrPages.Count; i++) { 9: 10: rtbDoc = (RichTextBox)docParent.arrPages[i]; 11: 12: if (i != docParent.intCurrentPage-1) { 13: rtbDoc.Focus(); 14: rtbDoc.SelectionStart = 0; 15: } 16: 17: intLocation = rtbDoc.Find(tbFind.Text, rtbDoc.SelectionStart + rtbDoc.SelectionLength, RichTextBoxFinds.None); 18: if (intLocation != -1) { 19: rtbDoc.SelectionStart = intLocation; 20: rtbDoc.SelectionLength = tbFind.Text.Length; 21: rtbDoc.Focus(); 22: break; 23: } 24: } 25: break; 26: case "Ersetzen": 27: for (int i = docParent.intCurrentPage-1; i < docParent.arrPages.Count; i++) { 28: 29: rtbDoc = (RichTextBox)docParent.arrPages[i];
719
Woche 3 – Rückblick
30: 31: if (i != docParent.intCurrentPage-1) { 32: rtbDoc.Focus(); 33: rtbDoc.SelectionStart = 0; 34: } 35: 36: intLocation = rtbDoc.Find(tbFind.Text, rtbDoc.SelectionStart + rtbDoc.SelectionLength, RichTextBoxFinds.None); 37: if (intLocation != -1) { 38: rtbDoc.SelectionStart = intLocation; 39: rtbDoc.SelectionLength = tbFind.Text.Length; 40: rtbDoc.SelectedText = tbReplace.Text; 41: rtbDoc.SelectionStart = intLocation; 42: rtbDoc.SelectionLength = tbReplace.Text.Length; 43: } 44: } 45: break; 46: case "Replace All": 47: for (int i = 0; i < docParent.arrPages.Count; i++) { 48: 49: rtbDoc = (RichTextBox)docParent.arrPages[i]; 50: 51: if (i != docParent.intCurrentPage-1) { 52: rtbDoc.Focus(); 53: rtbDoc.SelectionStart = 0; 54: } 55: 56: intLocation = rtbDoc.Find(tbFind.Text); 57: while (intLocation != -1) { 58: rtbDoc.SelectionStart = intLocation; 59: rtbDoc.SelectionLength = tbFind.Text.Length; 60: rtbDoc.SelectedText = tbReplace.Text; 61: intCount += 1; 62: intLocation = rtbDoc.Find(tbFind.Text, rtbDoc.SelectionStart + rtbDoc.SelectionLength, RichTextBoxFinds.None); 63: } 64: } 65: MessageBox.Show(intCount.ToString() + " Ersetzungen ausgeführt","Suchen"); 66: break; 67: case "Abbrechen": 68: this.Close(); 69: break; 70: } 71: }
720
Woche 3 – Rückblick
Der meiste Code hat sich nicht geändert. Im Grunde gibt es nur einen neuen Codeabschnitt, welcher jedoch an drei Stellen verwendet wird. In Zeile 8 findet sich eine for-Schleife, die durch jedes Page-Objekt in der Auflistung Document.arrPages iteriert. Jedes Page-Objekt wird vorübergehend der rtbDoc-Variablen in Zeile 10 zugewiesen. Diese temporäre Variable wird genau wie beim letzten Mal zum Suchen nach Text verwendet. Man kann jedoch ein RichTextBox-Steuerelement nur dann durchsuchen, wenn es den Eingabefokus besitzt. Daher geben wir in den Zeilen 12 bis 15 jedem Page-Objekt den Fokus, falls es ihn noch nicht hat. Dieser Code wird für den »Ersetzen«-Fall ab Zeile 27 wiederholt und ebenso für den Fall »Alle ersetzen« ab Zeile 47. Kompilieren Sie die revidierten Document- und FindDialog-Klassen (wie auch den Rest der Anwendung) mit folgendem Befehl: csc /t:winexe /r:system.dll /r:system.drawing.dll /r:system.windows.forms.dll / r:Page.dll NetWord.cs FindDialog.cs Document.cs
Dieser Befehl ähnelt den anderen, die Sie in den vorherigen Wochenrückblicken kennen gelernt haben. Sie rufen damit den C#-Compiler auf, wobei Sie auf die notwendigen Assemblies und die Quellcodedateien verweisen (beachten Sie den Verweis auf die Datei Page.dll). Abbildung R3.1 zeigt das Ergebnis dieser Anwendung nach der Erstellung einiger Seiten.
Abbildung R3.1: Ihre NetWordAnwendung verhält sich nun sehr ähnlich wie Microsoft Word.
721
Woche 3 – Rückblick
Probieren Sie die Anwendung aus. Gerät man ans Ende einer Seite, wird automatisch eine neue kreiert und die Cursorposition verschoben. Man kann Dokumente drucken (und sie in der Vorschau begutachten) – sie werden im Druck genau wie am Bildschirm erscheinen (eine Bildschirmseite entspricht also einer Druckseite). Meinen Glückwunsch zur neuen Textverarbeitung!
Der weitere Weg Obwohl nun eine komplette Textverarbeitung vorliegt, lassen sich noch einige Dinge daran tun, um NetWord effizienter und funktionsreicher zu machen. Die Art und Weise, wie man sich von einer Seite zur nächsten bewegt, entspricht nicht genau der in Word. Konkret: Man kann nicht hoch oder hinunter zur nächsten Seite springen, ohne explizit auf diejenige Seite zu klicken (in Word kann man einfach den Auf- bzw. Abwärts-Pfeil anklicken). Das wäre ein leicht zu behebendes Problem. Die Seitenzählfunktion funktioniert vorerst für neue Dokumente, doch öffnet man eine vorhandene Datei, so wird diese Datei nicht automatisch paginiert. Die Seitenzählung funktioniert bei normalem Inhalt, doch wenn man umfangreiche Textmengen einkopiert, wird dieser Text nicht korrekt paginiert. Vielmehr wird er am unteren Rand der aktuellen Seite angehängt, welche sich einfach ausdehnt, um den neuen Text aufzunehmen. Zudem lassen sich momentan keine Seiten löschen. In seiner aktuellen Implementierung kann NetWord nur reinen Text sowie RTF-Dateien bearbeiten. Doch mit Ihrer Kenntnis der Serialisierung (Tag 11 über Windows-Ein-/Ausgabe) können Sie Ihre eigenen Dateitypen erstellen. Auf diese Weise lässt sich ein Dokument mit intakten Seiten (in Page-Objekten) speichern, was Ihnen den erwähnten Prozess der neuerlichen Seitenzählung erspart, wenn man sie öffnet. Unter Verwendung von ActiveX (Tag 14) könnten Sie sogar NetWord einsetzen, um Word-Dokumente zu öffnen. NetWord könnte weitaus mehr Funktionen hinsichtlich der Bearbeitung von Dokumenten anbieten, so etwa die Fähigkeit, Text zu zentrieren oder rechtsbündig auszurichten, Zeilenabstände zu ändern, Aufzählungszeichen und nummerierte Listen einzufügen usw. Dieser Funktionsumfang befindet sich bereits im RichTextBox-Steuerelement, so dass man NetWord ohne weiteres damit ausstatten kann, indem man lediglich die entsprechende Benutzeroberfläche bereitstellt. Während man einige dieser Funktionen leicht integrieren kann, verlangen andere jedoch ein wenig Einfallsreichtum und technisches Wissen. Mit den in den letzten 21 Tagen erworbenen Kenntnissen können Sie diese Aufgaben jedoch zweifellos bewältigen. Danke, dass Sie die letzten 21 Tage mit uns verbracht haben.
722
Anhang A
2 2
Anhang A
A.1
Antworten auf die Quizfragen und Übungen
Tag 1 Quiz 1. Wahr oder falsch? Windows Forms ist Win32-Programmierung. Wahr. In diesem Buch besprechen wir keine Win16-Programmierung. 2. Wahr oder falsch? Metadaten enthalten Angaben darüber, in welcher Umgebung eine Anwendung erstellt wurde. Wahr. 3. Auf welche Weise ermöglicht MSIL plattformübergreifende Funktionsweise? MSIL ist eine plattformunabhängige Sprache, so dass sie sich leicht auf jedes Betriebssystem übertragen lässt. Ein JIT-Compiler wird dann eingesetzt, um die MSIL in die jeweilige Maschinensprache zu übertragen: ein wirtschaftlicherer Vorgang als für jede zu unterstützende Plattform die Anwendung neu zu schreiben oder zu kompilieren. 4. Wie nennt man die Weiterentwicklung von ActiveX Data Objects? ADO.NET. 5. Wahr oder falsch? Man kann Windows Forms-Anwendungen mit jedem beliebigen Texteditor erstellen. Wahr, solange die Dateien im Nur-Text-Format gespeichert werden. 6. Was ist ein Windows Forms-Steuerelement? Ein Windows Forms-Steuerelement ist ein Objekt, das dem Benutzer eine Schnittstelle bereitstellt, die häufig zur Interaktion fähig ist. 7. Was bewirkt das ImportsStatement? Es versetzt eine Anwendung in die Lage, die Objekte oder eine Auflistung von Objekten in anderen Namensräumen zu nutzen.
Übung Was würde der folgende Code bewirken, sobald er kompiliert wäre und ausgeführt würde? 1: 2:
724
Imports System Imports System.Windows.Forms
Antworten auf die Quizfragen und Übungen
3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15:
Public Class MyApp : Inherits Form Public Shared Sub Main() Application.Run(New MyApp) End Sub Public Sub New() Me.Height = 100 Me.Width = 50 End Sub End Class
Dies erzeugt ein Windows Form und verändert seine Größe auf 100 x 50 Pixel.
Tag 2 Quiz 1. Wie heißt die Basisklasse, von der alle anderen Klassen erben? Die Object-Klasse. 2. Was bewirken die Schlüsselwörter shared und static? Diese Schlüsselwörter zeigen an, dass ein Mitglied ein fundamentaler Klassenbestandteil ist, der in Instanzen der Klassen vorhanden ist. Während beispielsweise verschiedene Autoinstanzen unterschiedliche Farben haben können, so haben sie doch wohl alle den gleichen Metalltyp gemeinsam. 3. Wahr oder falsch? Man kann vorhandene Namensräume um eigene Klassen erweitern. Wahr. 4. Wahr oder falsch? Die Main-Methode wird jedes Mal ausgeführt, wenn eine neue Instanz ihrer Klasse erzeugt wird. Falsch. Die Main-Methode wird nur einmal ausgeführt, nämlich dann, wenn die Anwendung gestartet wird. 5. Aus welchem Grund ist der folgende Code nicht ausreichend? Was muss außerdem verwendet werden? dim MyObject as Button
725
Anhang A
Er ist nicht ausreichend, weil man damit nur eine Variable des Typs Button deklariert; man weist der Variablen keinen Wert zu, was zu erledigen ist, bevor man sie einsetzen kann. Um die Variable zu benutzen, ist das New-Schlüsselwort einzusetzen: dim MyObject as New Button
oder dim MyObject as Button = New Button
6. Was stimmt mit dem folgenden Kompilierungsbefehl nicht? (Tipp: Er kann mehr als einen Fehler enthalten.) csc /type:windowsexe /r:system.dll /r:system.drawing.dll / r:system.drawing.text.dll dateiname.vb
Es gibt keinen Typ windowsexe. Verwenden Sie statt dessen winexe. Es gibt auch keine Assembly system.drawing.text.dll. Der Namensraum system.drawing.text ist in der Assembly system.drawing.dll enthalten. Außerdem verwendet diese Codezeile den C#-Compiler, um eine VB .NET-Datei zu kompilieren, wie an der Erweiterung der Quellcodedatei zu erkennen ist. 7. Was bedeuten die folgenden Eigenschaften? Button.CanSelect: Gibt an, ob die Schaltfläche vom Benutzer ausgewählt werden
kann. Button.Cursor: Gibt das Symbol an, das benutzt wird, wenn sich der Mauszeiger über der Schaltfläche befindet. Form.AllowDrop: Gibt an, ob dieses Formular Drag & Drop verarbeiten kann. Form.Visible: Ist das Formular für den Benutzer sichtbar?
8. Nennen Sie drei semantische Unterschiede zwischen C# und Visual Basic .NET. Variablentypen kommen in C# vor Variablennamen. Am Ende jeder Zeile muss ein Semikolon gesetzt werden. C# beachtet die Groß-/Kleinschreibung. 9. Wahr oder falsch? Jede Klasse muss einen Konstruktor haben. Das ist eine Fangfrage. Es trifft zu, dass jede Klasse einen Konstruktor haben muss, aber man muss keinen explizit selbst erzeugen. Die CLR erstellt nämlich automatisch einen, wenn er noch nicht vorhanden ist.
726
Antworten auf die Quizfragen und Übungen
Übung Erweitern Sie das heutige Taschenrechner-Beispiel. Fügen Sie weitere Schaltflächen hinzu, mit denen sich arithmetische Operationen ausführen lassen: Subtraktion, Multiplikation und Division. Versuchen Sie, die Anwendung sowohl in C# als auch in Visual Basic .NET zu erstellen. Verschönern Sie die Benutzeroberfläche Ihres Taschenrechners durch den großzügigen Gebrauch von Bezeichnungsfeldern (Label-Steuerelementen). Der Code in C#: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36:
using System; using System.Windows.Forms; using System.Drawing; namespace TYWinforms.Day2 { public class Calculator : Form { private Button btnAdd; private Button btnSubtract; private Button btnMultiply; private Button btnDivide; private TextBox tbNumber1; private TextBox tbNumber2; private Label lblAnswer; private Label lblNum1; private Label lblNum2; public static void Main() { Application.Run(new Calculator()); } public Calculator() { this.btnAdd = new Button(); this.btnSubtract = new Button(); this.btnMultiply = new Button(); this.btnDivide = new Button(); this.tbNumber1 = new TextBox(); this.tbNumber2 = new TextBox(); this.lblAnswer = new Label(); this.lblNum1 = new Label(); this.lblNum2 = new Label(); this.Width = 325; this.Height = 150; this.Text = "My Calculator";
727
Anhang A
37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80: 81: 82:
728
tbNumber1.Location = new Point(100,0); tbNumber2.Location = new Point(100,25); btnAdd.Location = new Point(0,75); btnAdd.Text = "+"; btnAdd.Click += new EventHandler(this.Add); btnSubtract.Location = new Point(75,75); btnSubtract.Text = "-"; btnSubtract.Click += new EventHandler(this.Subtract); btnMultiply.Location = new Point(150,75); btnMultiply.Text = "*"; btnMultiply.Click += new EventHandler(this.Multiply); btnDivide.Location = new Point(225,75); btnDivide.Text = "/"; btnDivide.Click += new EventHandler(this.Divide); lblNum1.Text = "Enter number 1: "; lblNum1.Location = new Point(0,0); lblNum2.Text = "Enter number 2: "; lblNum2.Location = new Point(0,25); lblAnswer.Location = new Point(0,55); this.Controls.Add(btnAdd); this.Controls.Add(btnSubtract); this.Controls.Add(btnMultiply); this.Controls.Add(btnDivide); this.Controls.Add(tbNumber1); this.Controls.Add(tbNumber2); this.Controls.Add(lblAnswer); this.Controls.Add(lblNum1); this.Controls.Add(lblNum2); } public void Add(object Sender, EventArgs e) { lblAnswer.Text = Convert.ToString(Convert.ToInt32(tbNumber1.Text) + Convert.ToInt32(tbNumber2.Text)); } public void Subtract(object Sender, EventArgs e) { lblAnswer.Text = Convert.ToString(Convert.ToInt32(tbNumber1.Text) Convert.ToInt32(tbNumber2.Text));
Antworten auf die Quizfragen und Übungen
83: 84: 85: 86: 87: 88: 89: 90: 91: 92: 93: 94: 95:
} public void Multiply(object Sender, EventArgs e) { lblAnswer.Text = Convert.ToString(Convert.ToInt32(tbNumber1.Text) * Convert.ToInt32(tbNumber2.Text)); } public void Divide(object Sender, EventArgs e) { lblAnswer.Text = Convert.ToString(Convert.ToInt32(tbNumber1.Text) / Convert.ToInt32(tbNumber2.Text)); } } }
Der Code in VB .NET: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31:
Imports System Imports System.Windows.Forms Imports System.Drawing namespace TYWinforms.Day2 Public class Calculator : Inherits Form private WithEvents btnAdd as Button private WithEvents btnSubtract as Button private WithEvents btnMultiply as Button private WithEvents btnDivide as Button private tbNumber1 as TextBox private tbNumber2 as TextBox private lblAnswer as Label private lblNum1 as Label private lblNum2 aS Label Public shared Sub Main() Application.Run(new Calculator) End Sub Public Sub New() Me.btnAdd = new Button() Me.btnSubtract = new Button() Me.btnMultiply = new Button() Me.btnDivide = new Button() Me.tbNumber1 = new TextBox() Me.tbNumber2 = new TextBox() Me.lblAnswer = new Label() Me.lblNum1 = new Label() Me.lblNum2 = new Label()
729
Anhang A
32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77:
730
Me.Width = 325 Me.Height = 150 Me.Text = "My Calculator" tbNumber1.Location = new Point(100,0) tbNumber2.Location = new Point(100,25) btnAdd.Location = new Point(0,75) btnAdd.Text = "+" AddHandler btnAdd.Click, new EventHandler (AddressOf Add)
btnSubtract.Location = new Point(75,75) btnSubtract.Text = "-" AddHandler btnSubtract.Click, new EventHandler(AddressOf Subtract) btnMultiply.Location = new Point(150,75) btnMultiply.Text = "*" AddHandler btnMultiply.Click, new EventHandler (AddressOf Multiply) btnDivide.Location = new Point(225,75) btnDivide.Text = "/" AddHandler btnDivide.Click, new EventHandler (AddressOf Divide) lblNum1.Text = "Enter number 1: " lblNum1.Location = new Point(0,0) lblNum2.Text = "Enter number 2: " lblNum2.Location = new Point(0,25) lblAnswer.Location = new Point(0,55) Me.Controls.Add(btnAdd) Me.Controls.Add(btnSubtract) Me.Controls.Add(btnMultiply) Me.Controls.Add(btnDivide) Me.Controls.Add(tbNumber1) Me.Controls.Add(tbNumber2) Me.Controls.Add(lblAnswer) Me.Controls.Add(lblNum1) Me.Controls.Add(lblNum2) End Sub Public Sub Add(ByVal Sender as Object, ByVal e as EventArgs) lblAnswer.Text = CStr(Cint(tbNumber1.Text) + Cint(tbNumber2.Text))
Antworten auf die Quizfragen und Übungen
78: 79: 80: 81: 82: 83: 84: 85: 86: 87: 88: 89: 90: 91: 92:
End Sub Public Sub Subtract(ByVal Sender as Object, ByVal e as EventArgs) lblAnswer.Text = CStr(Cint(tbNumber1.Text) – Cint(tbNumber2.Text)) End Sub Public Sub Multiply(ByVal Sender as Object, ByVal e as EventArgs) lblAnswer.Text = CStr(Cint(tbNumber1.Text) * Cint(tbNumber2.Text)) End Sub Public Sub Divide(ByVal Sender as Object, ByVal e as EventArgs) lblAnswer.Text = CStr(Cint(tbNumber1.Text) / Cint(tbNumber2.Text)) End Sub End Class End Namespace
Tag 3 Quiz Die Fragen 1 bis 3 beziehen sich auf das folgende Codefragment: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17:
using System; using System.Windows.Forms; using System.Drawing; public class MyForm : Form { private Label lblMessage = new Label(); public MyForm() { this.Text = „Hello World!"; } } public class StartForm { public static void Main() { Application.Run(new MyForm()); } }
1. Was würde das folgende Statement liefern, wenn man es unter Zeile 9 platzieren würde? Console.Write(this.ToString()); MyForm, Text: Hello World!
731
Anhang A
2. Was würde der folgende Code liefern, wenn man ihn unter Zeile 9 platzieren würde? Label lblTemp = new Label(); Console.Write(lblTemp.Equals(this.lblMessage).ToString()); False.
3. Was würde der folgende Code liefern, wenn man ihn unter Zeile 9 platzieren würde? Label lblTemp = new Label(); Console.Write(Object.ReferenceEquals(lblTemp, this.lblMessage).ToString()); False.
4. Wahr oder falsch? Das KeyPress-Ereignis erfordert einen Handler vom Typ KeyEventHandler. Falsch. Die KeyPress-Ereignisse übernehmen einen KeyPressEventHandler. 5. Nennen Sie die fünf Eigenschaften des Objekts MouseEventArgs. Button, Clicks, Delta, X und Y.
6. Schreiben Sie ein einzelnes Statement in VB .NET, das die Breite eines Formulars auf ein Drittel der Bildschirmhöhe festlegt. Me.Width = Screen.GetWorkingArea(Me).Height / 3
7. Welche Eigenschaft kontrolliert, welche Schaltfläche aktiviert wird, sobald der Benutzer die (ESC)-Taste gedrückt hat? CancelButton.
8. Welches ist der Standardwert von FormBorderStyle? Sizable.
9. Welche drei Ereignisse verwenden das Paradigma, dass mit einer einzelnen Aktion zwei Ereignisse verknüpft sind? Closed, InputLanguageChanged und Validated.
Übung 1. Erstellen Sie in C# eine Anwendung, die alle sechs Mausereignisse überwacht. Zeigen Sie eine Meldung in einem Textfeld an, wenn die einzelnen Ereignisse auftreten. 2. Erstellen Sie in VB .NET eine Anwendung, mit der die Benutzer die Eigenschaften Text, Height, Width und Opacity durch Werteingaben in eine TextBox und das Drücken einer EINGEBEN-Schaltfläche anpassen können.
732
Antworten auf die Quizfragen und Übungen
Zu 1: 1: using System; 2: using System.Windows.Forms; 3: using System.Drawing; 4: using System.ComponentModel; 5: 6: namespace TYWinForms.Day3 { 7: 8: public class Exercise1 : Form { 9: private Label lblMessage = new Label(); 10: 11: public Exercise1() { 12: lblMessage.Width = this.Width; 13: lblMessage.Height = this.Height; 14: 15: this.Text = "Mouse events Example"; 16: this.Width = 800; 17: this.Height = 600; 18: this.MouseEnter += new EventHandler(this.MouseEntered); 19: this.MouseHover += new EventHandler(this.MouseHovered); 20: this.MouseLeave += new EventHandler(this.MouseLeft); 21: this.MouseMove += new MouseEventHandler(this.MouseMoved); 22: this.MouseDown += new MouseEventHandler(this.MouseClicked); 23: this.MouseUp += new MouseEventHandler(this.MouseReleased); 24: 25: this.Controls.Add(lblMessage); 26: } 27: 28: public void MouseEntered(Object Sender, EventArgs e) { 29: lblMessage.Text += "Mouse entered\r\n"; 30: } 31: 32: public void MouseHovered(Object Sender, EventArgs e) { 33: lblMessage.Text += "Mouse hovered\r\n"; 34: } 35: 36: public void MouseLeft(Object Sender, EventArgs e) { 37: lblMessage.Text += "Mouse left\r\n"; 38: } 39: 40: public void MouseMoved(Object Sender, MouseEventArgs e) { 41: lblMessage.Text += "Mouse moved: x=" + e.X + ", y=" + e.Y + "\r\n"; 42: } 43:
733
Anhang A
44: 45: 46: 47: 48: 49: "\r\n"; 50: 51: 52: 53: 54: 55: } 56: }
public void MouseClicked(Object Sender, MouseEventArgs e) { lblMessage.Text += "Button clicked: " + e.Button + "\r\n"; } public void MouseReleased(Object Sender, MouseEventArgs e) { lblMessage.Text += "Button released: x=" + e.X + ", y=" + e.Y + } public static void Main() { Application.Run(new Exercise1()); }
Zu 2. Der Code für die Anwendung sieht wie folgt aus: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30:
734
Imports Imports Imports Imports
System System.Windows.Forms System.Drawing System.ComponentModel
Namespace TYWinForms.Day3 public class Exercise2 : Inherits Form private btSubmit as new Button private tbText as new TextBox private tbHeight as new TextBox private tbWidth as new TextBox private tbOpacity as new TextBox private lblText as new Label private lblHeight as new Label private lblWidth as new Label private lblOpacity as new Label public sub New() Me.Text = "Event Example" Me.Width = 800 Me.Height = 600 btSubmit.Location = new Point(50, 150) btSubmit.Text = "Submit" AddHandler btSubmit.Click, AddressOf Me.HandleIt lblText.Location = new Point(25,25) lblText.Text = "Text: " tbText.Location = new Point(75,25)
Antworten auf die Quizfragen und Übungen
31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75:
tbText.Text = Me.Text lblHeight.Location = new Point(25,50) lblHeight.Text = "Height: " tbHeight.Location = new Point(75,50) tbHeight.Text = Me.Height lblWidth.Location = new Point(25,75) lblWidth.Text = "Width: " tbWidth.Location = new Point(75,75) tbWidth.Text = Me.Width lblOpacity.Location = new Point(25,100) lblOpacity.Text = "Opacity: " tbOpacity.Location = new Point(75,100) tbOpacity.Text = Me.Opacity Me.Controls.Add(btSubmit) Me.Controls.Add(tbText) Me.Controls.Add(tbHeight) Me.Controls.Add(tbWidth) Me.Controls.Add(tbOpacity) Me.Controls.Add(lblText) Me.Controls.Add(lblHeight) Me.Controls.Add(lblWidth) Me.Controls.Add(lblOpacity) end sub public sub HandleIt(Sender as Object, e as EventArgs) Me.Text = tbText.Text Me.Height = CInt(tbHeight.Text) Me.Width = CInt(tbWidth.Text) Me.Opacity = CInt(tbOpacity.Text) end sub end class
public class StartForm public shared sub Main() Application.Run(new Exercise2) end sub end class End Namespace
735
Anhang A
Tag 4 Quiz 1. Wahr oder falsch? Alle Steuerelemente inklusive des Form-Objekts erben von der Klasse Control. Wahr. 2. Welches Objekt muss mit Symbolleisten verknüpft sein, damit Grafiken in den Symbolleistenschaltflächen angezeigt werden? Das ImageList-Objekt. 3. Wie heißen die drei optionalen Parameter für einen MenuItem-Konstruktor, und welches sind ihre Typen? Der erste Parameter ist eine Zeichenfolge, die die Aufschrift (oder Text-Eigenschaft) des MenuItem-Steuerelements darstellt. Der 2. Parameter legt den Ereignishandler fest, der auszuführen ist, sobald das MenuItem angeklickt wurde; es handelt sich um ein EventHandler-Objekt. Der 3. Parameter ist ein Wert aus der ShortCut-Aufzählung, der die Tastenkombination festlegt, die für den Zugriff auf das Menüelement benutzt werden kann. 4. Welches Zeichen wird verwendet, um eine Tastenkombination für einen Buchstaben in der Beschriftung eines Menüelements bereitzustellen? Das kaufmännische Und-Zeichen (&, auch Ampersand genannt). 5. Schreiben Sie eine Zeile C#-Code, die ein ToolBarButton-Steuerelement namens MyFirstButton anweist, die vierte Grafik in der zugeordneten ImageList zu verwenden. myFirstButton.ImageIndex = 3;
6. Wahr oder falsch? Das Ereignis, das für die Behandlung von Mausklicks auf Symbolleistenschaltflächen verwendet wird, heißt Click. Falsch. Das Ereignis heißt ButtonClick und gehört zum ToolBar-Objekt. 7. Welche sind die Standardwerte für die Eigenschaften Minimum, Maximum, SmallChange und LargeChange einer Bildlaufleiste? Jeweils 0, 100, 1 und 10.
Übung Erstellen Sie in VB .NET eine Anwendung, die ein personalisiertes Menü verwendet wie jene, die mit Microsoft Office 2000 und Windows 2000 eingeführt wurden. Diese Menüs zeigen
736
Antworten auf die Quizfragen und Übungen
nur die zuletzt benutzten Menüelemente, während sie die anderen verbergen und es dem Benutzer gestatten, auf einen Pfeil zu klicken, um weniger häufig ausgewählte Menüelemente anzuzeigen. Ihre Anwendung sollte ein Menü anbieten, das verborgene Menüelemente besitzt. Wird ein bestimmtes Menüelement angeklickt, sollen die verborgenen Elemente angezeigt werden. Kümmern Sie sich nicht um das Hinzufügen von Ereignishandlern für jedes Menüelement. Ihr Menü kann recht einfach sein: Es braucht sich nicht daran zu erinnern, welche Menüelemente der Benutzer am häufigsten auswählt, und es wird keine verborgenen Menüelemente anzeigen, nur weil der Mauspfeil über dem Menü schwebt. Sobald der Benutzer auf die Schaltfläche WEITERE... (More) klickt, muss er das Menü erneut öffnen, um die bislang verborgenen Elemente sehen zu können. (Sie werden sehen, wie man fortgeschrittenere Funktionen verwenden kann, wenn Sie an Tag 13 etwas über GDI+ erfahren. Der Code für die Anwendung sieht wie folgt aus: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30:
Imports System Imports System.Windows.Forms Namespace TYWinForms.Day4 public class Exercise1 : Inherits Form private mnuFile as new MainMenu private miFile as new MenuItem("File") private miOpen as new MenuItem("Open...") private miClose as new MenuItem("Close") private miPrintPreview as new MenuItem("Print Preview") private miPrint as new MenuItem("Print...") private miProperties as new MenuItem("Properties") private miSendTo as new MenuItem("Send To...") private miExit as new MenuItem("Exit") private WithEvents miMore as new MenuItem("More items...") public event PopUp as EventHandler public sub New() Me.Text = "Exercise 1" Me.Menu = mnuFile CreateMenus() End Sub public sub CreateMenus() mnuFile.MenuItems.Add(miFile)
737
Anhang A
31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67:
738
miFile.MenuItems.Add(miOpen) miFile.MenuItems.Add(miClose) miFile.MenuItems.Add("-") miFile.MenuItems.Add(miPrintPreview) miFile.MenuItems.Add(miPrint) miFile.MenuItems.Add("-") miFile.MenuItems.Add(miSendTo) miFile.MenuItems.Add(miProperties) miFile.MenuItems.Add("-") miFile.MenuItems.Add(miExit) miFile.MenuItems.Add("-") miFile.MenuItems.Add(miMore) miPrintPreview.Visible = false miProperties.Visible = false miSendTo.Visible = false miFile.MenuItems(5).Visible = false AddHandler miMore.Click, new EventHandler(AddressOf Me.ShowMenu) end sub public sub ShowMenu(Sender as Object, e as EventArgs) miPrintPreview.Visible = true miProperties.Visible = true miSendTo.Visible = true miFile.MenuItems(5).Visible = true miFile.MenuItems(10).Visible = false miMore.Visible = false end sub public Shared Sub Main() Application.Run(new Exercise1) end sub end class End Namespace
Antworten auf die Quizfragen und Übungen
Tag 5 Quiz 1. Wie sieht die standardmäßige Signatur eines Ereignishandlers aus? public sub HandlerName(Object as Sender, e as EventArgs)
2. Erzeugen Sie in C# ein Ereignis namens ShowText, das den Delegaten KeyPressEventHandler verwendet. public event KeyPressEventHandler ShowText;
3. Erstellen Sie einen benutzerdefinierten Delegaten, der das Objekt KeyPressEventArgs verwendet. public delegate void CustomEventHandler(Object Sender, KeyPressEventArgs e);
4. Wahr oder falsch? Um Objekttypen miteinander zu vergleichen, verwendet man den Gleichheitszeichenoperator (=). Falsch. Das Gleichheitszeichen wird nur bei einfachen Datentypen eingesetzt. Für Objekte sollten Sie den is-Operator benutzen. 5. Welche der folgenden Aussagen ist inkorrekt? AddHandler btOne.Click, new EventHandler(AddressOf btOne_Click) public sub MyEventHandler(Sender as Object, e as EventArgs) Handles btTemp.Click Addhandler btOne.Click += new EventHandler(AddressOf btOne_Click)
Das dritte Statement ist falsch, denn es versucht, sowohl C#- als auch VB .NET-Syntax zu kombinieren. 6. Warum sollten die Eigenschaften eines benutzerdefinierten EventArgs-Objekts readonly sein? Der Ereignishandler, also die das benutzerdefinierte EventArgs-Objekt empfangende Methode, sollte nicht in der Lage sein, diese Eigenschaften zu verändern. Sie sollten vom Ereignis erzeugt werden, um dem Handler ergänzende Informationen zu liefern. 7. Ist das folgende Codestück korrekt? Was sollte man falls nötig ändern? public virtual void OnMyEvent(EventArgs e) { MyEvent(this, e); }
Erstens muss der Zugriffsmodifizierer von public auf protected geändert werden; nur die Klasse, in der sich diese Methode befindet, sollte ihn ausführen können und public
739
Anhang A
würde dies jeder Klasse erlauben. Zweitens sollte es eine Prüfung geben, um herauszufinden, ob ein Delegat zugewiesen wurde: if (MyEvent != null) { MyEvent(this, e); }
8. An welcher Stelle (im Quellcode) muss man benutzerdefinierte Ereignisse und Delegaten deklarieren? Ein Ereignis muss innerhalb derjenigen Klasse deklariert werden, die das Ereignis auslösen soll, jedoch außerhalb jeglicher Methodendeklaration. Ein Delegat kann sowohl inner- als auch außerhalb einer Klasse deklariert werden.
Übung Erstellen Sie in C# eine Taschenrechner-Anwendung wie den Windows-Taschenrechner. Er sollte Zifferntasten aufweisen, mit denen sich Zahlen eingeben lassen, sowie Funktionstasten, die Berechnungen vornehmen, wenn man darauf klickt. Verwenden Sie einen Ereignishandler für alle Zifferntasten und einen Handler für alle Funktionstasten. (Tipp: Nehmen Sie verborgene Steuerelemente, also solche, deren Visible-Eigenschaft auf False gesetzt ist – damit können Sie temporäre Variablen speichern.) 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23:
740
using System; using System.Windows.Forms; using System.Drawing; namespace TYWinforms.Day5 { public class Calculator : Form { private TextBox tbNumber = new TextBox(); private TextBox tbHiddenNumber = new TextBox(); private TextBox tbHiddenOperator = new TextBox(); private TextBox tbHiddenAnswer = new TextBox(); private private private private private private private private private private
Button Button Button Button Button Button Button Button Button Button
btZero = new Button(); btOne = new Button(); btTwo = new Button(); btThree = new Button(); btFour = new Button(); btFive = new Button(); btSix = new Button(); btSeven = new Button(); btEight = new Button(); btNine = new Button();
Antworten auf die Quizfragen und Übungen
24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69:
private private private private private
Button Button Button Button Button
btAdd = new Button(); btSubtract = new Button(); btMultiply = new Button(); btDivide = new Button(); btEquals = new Button();
public Calculator() { tbNumber.Location = new Point(210,10); tbNumber.Width = 100; tbHiddenNumber.Visible = false; tbHiddenOperator.Visible = false; tbHiddenAnswer.Visible = false; btZero.Text = "0"; btZero.Location = new Point(10,125); btZero.Click += new EventHandler(this.NumberClick); btOne.Text = "1"; btOne.Location = new Point(10,100); btOne.Click += new EventHandler(this.NumberClick); btTwo.Text = "2"; btTwo.Location = new Point(85,100); btTwo.Click += new EventHandler(this.NumberClick); btThree.Text = "3"; btThree.Location = new Point(160,100); btThree.Click += new EventHandler(this.NumberClick); btFour.Text = "4"; btFour.Location = new Point(10,75); btFour.Click += new EventHandler(this.NumberClick); btFive.Text = "5"; btFive.Location = new Point(85,75); btFive.Click += new EventHandler(this.NumberClick); btSix.Text = "6"; btSix.Location = new Point(160,75); btSix.Click += new EventHandler(this.NumberClick); btSeven.Text = "7"; btSeven.Location = new Point(10,50); btSeven.Click += new EventHandler(this.NumberClick);
741
Anhang A
70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80: 81: 82: 83: 84: 85: 86: 87: 88: 89: 90: 91: 92: 93: 94: 95: 96: 97: 98: 99: 100: 101: 102: 103: 104: 105: 106: 107: 108: 109: 110: 111: 112: 113: 114: 115:
742
btEight.Text = "8"; btEight.Location = new Point(85,50); btEight.Click += new EventHandler(this.NumberClick); btNine.Text = "9"; btNine.Location = new Point(160,50); btNine.Click += new EventHandler(this.NumberClick); btAdd.Text = "+"; btAdd.Location = new Point(160,125); btAdd.Click += new EventHandler(this.OperatorClick); btSubtract.Text = "-"; btSubtract.Location = new Point(235,100); btSubtract.Click += new EventHandler(this.OperatorClick); btMultiply.Text = "*"; btMultiply.Location = new Point(235,75); btMultiply.Click += new EventHandler(this.OperatorClick); btDivide.Text = "/"; btDivide.Location = new Point(235,50); btDivide.Click += new EventHandler(this.OperatorClick); btEquals.Text = "="; btEquals.Location = new Point(235,125); btEquals.Click += new EventHandler(this.OperatorClick);
this.Text = "Calculator"; this.Width = 325; this.Height = 200; this.Controls.Add(btZero); this.Controls.Add(btOne); this.Controls.Add(btTwo); this.Controls.Add(btThree); this.Controls.Add(btFour); this.Controls.Add(btFive); this.Controls.Add(btSix); this.Controls.Add(btSeven); this.Controls.Add(btEight); this.Controls.Add(btNine); this.Controls.Add(btAdd); this.Controls.Add(btSubtract); this.Controls.Add(btMultiply); this.Controls.Add(btDivide);
Antworten auf die Quizfragen und Übungen
116: this.Controls.Add(btEquals); 117: this.Controls.Add(tbNumber); 118: this.Controls.Add(tbHiddenOperator); 119: this.Controls.Add(tbHiddenNumber); 120: this.Controls.Add(tbHiddenAnswer); 121: } 122: 123: public void NumberClick(Object Sender, EventArgs e) { 124: if (tbNumber.Text != "" & tbNumber.Text != "0" & tbHiddenAnswer.Text != "1") { 125: tbNumber.Text += ((Button)Sender).Text; 126: } else { 127: tbNumber.Text = ((Button)Sender).Text; 128: tbHiddenAnswer.Text = ""; 129: } 130: } 131: 132: public void OperatorClick(Object Sender, EventArgs e) { 133: int intAnswer = 0; 134: 135: if (tbNumber.Text != "" & ((Button)Sender).Text != "=") { 136: tbHiddenNumber.Text = tbNumber.Text; 137: tbHiddenOperator.Text = ((Button)Sender).Text; 138: tbNumber.Text = "0"; 139: } else if (tbNumber.Text != "" & ((Button)Sender).Text == 140: switch(tbHiddenOperator.Text) { 141: case "+": 142: intAnswer = Convert.ToInt32 (tbHiddenNumber.Text) Convert.ToInt32(tbNumber.Text); 143: break; 144: case "-": 145: intAnswer = Convert.ToInt32 (tbHiddenNumber.Text) Convert.ToInt32(tbNumber.Text); 46: break; 147: case "*": 148: intAnswer = Convert.ToInt32 (tbHiddenNumber.Text) Convert.ToInt32(tbNumber.Text); 149: break; 150: case "/": 151: intAnswer = Convert.ToInt32 (tbHiddenNumber.Text) Convert.ToInt32(tbNumber.Text); 152: break; 153: } 154: tbNumber.Text = intAnswer.ToString(); 155: tbHiddenAnswer.Text = "1"; 156: tbHiddenNumber.Text = "";
"=") {
+
-
*
/
743
Anhang A
157: 158: 159: 160: 161: 162: 163: 164: 165:
tbHiddenOperator.Text = ""; } } public static void Main() { Application.Run(new Calculator()); } } }
Tag 6 Quiz 1. Wodurch werden RadioButton-Auswahlen begrenzt? Der Benutzer kann nur ein RadioButton-Steuerelement pro Containersteuerelement auswählen. 2. Was bedeutet die folgende Datums-/Zeit-Formatzeichenfolge? "hh:MM:yy-dddd"
Zwei-Ziffern-Stundenanzeige, gefolgt von einem Doppelpunkt und dem Zwei-ZiffernMonat, wiederum gefolgt von einem Doppelpunkt und einer Zwei-Ziffern-Jahreszahl, gefolgt von einem Gedankenstrich und dem ausgeschriebenen Wochentag. 3. Welches Objekt ist mit dem TreeView-Steuerelement verknüpft? Das TreeNode-Objekt, welches zur Darstellung jedes Knotens in der Strukturansicht benutzt wird. 4. Setzen Sie für ein Button-Steuerelement namens btHappy Eigenschaften, die es dazu veranlassen, senkrecht zu expandieren, sobald sein Containerformular seine Größe ändert. btHappy.Anchor = AnchorStyles.Top Or AnchorStyles.Bottom
5. Nennen Sie zehn Mitglieder, die allen heute besprochenen Steuerelementen gemeinsam sind! Die Eigenschaften Anchor, Dock, BackColor, Location, Font und Text, die Methoden GetType und Refresh sowie die Ereignisse Click und Resize. 6. Wie heißen die fünf Mitglieder des Timer-Steuerelements? Enabled, Interval, Tick, Start und Stop.
744
Antworten auf die Quizfragen und Übungen
7. Wie fügt man dem Steuerelement TabControl Tabseiten hinzu? Beschreiben Sie dies in Worten und mit Hilfe einer Zeile Code. Man verwendet TabPage-Objekte, um weitere Tabseiten hinzuzufügen. Im folgenden Beispiel sei ein TabControl-Steuerelement namens MyTabControl gegeben: MyTabControl.TabPages.Add(New TabPage("Label text"));
8. Welche beiden Methoden sollte man ausführen, wenn man vorhat, einer ComboBox mit Hilfe der Add-Methode viele Elemente hinzuzufügen? Wenn Sie an Ihr Wissen über Windows Forms-Steuerelemente denken: Welche anderen heute vorgestellten Steuerelemente haben Ihrer Ansicht nach ebenfalls diese Methoden? Die BeginUpdate-Methode wird benutzt, um das Steuerelement am Aktualisieren seiner Anzeige zu hindern, bis die EndUpdate-Methode aufgerufen wird. Neben der ComboBox sind auch die ListBox- und TreeView-Steuerelemente in der Lage, diese Methoden einzusetzen. 9. Wahr oder falsch? Das PictureBox-Steuerelement kann nur Bitmap- (.BMP) und GIFGrafiken anzeigen. Falsch. Das PictureBox-Steuerelement kann diese ebenso anzeigen wie viele andere Formate, beispielsweise .JPG- und .ICO-Dateien.
Übung Erstellen Sie in C# eine Anwendung, mit deren Hilfe Benutzer ihre CD-Sammlung katalogisieren und betrachten können. Sie müssen neue Künstler/Alben/Stücke in den Katalog aufnehmen und mit einer hierarchischen Liste betrachten können. (Kümmern Sie sich nicht um das Speichern der Eingaben – das besprechen wir an Tag 9, wenn es um ADO.NET geht.) Denken Sie daran, dass Alben nur Künstlern hinzugefügt werden und Stücke nur Alben. 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15:
using System; using System.Windows.Forms; using System.Drawing; namespace TYWinForms.Day6 { public class CDCatalog : Form { private TreeView tvList = new TreeView(); private TextBox tbName = new TextBox(); private Button btArtist = new Button(); private Button btAlbum = new Button(); private Button btSong = new Button(); public CDCatalog() { tvList.Location = new Point(10,10);
745
Anhang A
16: tvList.Size = new Size(200,550); 17: tvList.LabelEdit = true; 18: 19: tbName.Location = new Point(250,10); 20: tbName.Width = 150; 21: 22: btArtist.Location = new Point(225,40); 23: btArtist.Text = "Add Artist"; 24: btArtist.Click += new EventHandler(this.AddArtist); 25: 26: btAlbum.Location = new Point(300,40); 27: btAlbum.Text = "Add Album"; 28: btAlbum.Click += new EventHandler(this.AddAlbum); 29: 30: btSong.Location = new Point(375,40); 31: btSong.Text = "Add Song"; 32: btSong.Click += new EventHandler(this.AddSong); 33: 34: this.Text = "CD Catalog"; 35: this.Size = new Size(800,600); 36: this.Controls.Add(tvList); 37: this.Controls.Add(tbName); 38: this.Controls.Add(btArtist); 39: this.Controls.Add(btAlbum); 40: this.Controls.Add(btSong); 41: } 42: 43: public void AddArtist(Object Sender, EventArgs e) { 44: if (tbName.Text == "") { 45: MessageBox.Show("You forgot to enter a name"); 46: return; 47: } 48: 49: tvList.Nodes.Add(new TreeNode(tbName.Text)); 50: tbName.Text = ""; 51: } 52: 53: public void AddAlbum(Object Sender, EventArgs e) { 54: if (tbName.Text == "") { 55: MessageBox.Show("You forgot to enter a name"); 56: return; 57: } 58: 59: if (tvList.SelectedNode != null & tvList.Nodes.Contains(tvList.SelectedNode)) { 60: tvList.SelectedNode.Nodes.Add(new TreeNode (tbName.Text));
746
Antworten auf die Quizfragen und Übungen
61: tbName.Text = ""; 62: tvList.ExpandAll(); 63: } else { 64: MessageBox.Show("You must first select an artist"); 65: } 66: } 67: 68: public void AddSong(Object Sender, EventArgs e) { 69: if (tbName.Text == "") { 70: MessageBox.Show("You forgot to enter a name"); 71: return; 72: } 73: 74: if (tvList.SelectedNode != null & tvList.Nodes.Contains(tvList.SelectedNode.Parent)) { 75: tvList.SelectedNode.Nodes.Add(new TreeNode (tbName.Text)); 76: tbName.Text = ""; 77: tvList.ExpandAll(); 78: } else { 79: MessageBox.Show("You must first select an album"); 80: } 81: } 82: 83: public static void Main() { 84: Application.Run(new CDCatalog()); 85: } 86: } 87: }
Tag 7 Quiz 1. Wahr oder falsch? Jedes Form-Objekt kann ein Dialogfeld sein. Wahr. 2. Wie macht man ein Dialogfeld modal bzw. nichtmodal? Durch Aufruf der Methode ShowDialog bzw. durch Aufruf der Methode Show. 3. Wahr oder falsch? Sie können ein MessageBox-Objekt direkt instantiieren. Falsch. Man kann nur auf die statische Show-Methode dieses Objekts zugreifen.
747
Anhang A
4. Wie heißen die sieben Parameter, die die MessageBox.Show-Methode übernehmen kann? (Führen Sie nur ihre Typen mit einer kurzen Beschreibung auf.) 왘
Iwin32Window: Das Fenster, vor dem das Meldungsfeld angezeigt wird
왘
String: Der im Meldungsfeld anzuzeigende Text
왘
String: Die Titelzeile des Fensters
왘
MessageBoxButtons: Die Schaltflächen, die im Meldungsfeld erscheinen sollen
왘
MessageBoxIcon: Das anzuzeigende Symbol
왘
MessageBoxDefaultButton: Die vorausgewählte Schaltfläche
왘
MessageBoxOptions: Diverse nicht damit verknüpfte Eigenschaften
5. Welche zwei Vorteile erzielen Sie, wenn Sie in Ihrer benutzerdefinierten Dialogklasse einen DialogResult-Wert zuweisen? Es liefert dem übergeordneten Formular einen DialogResult-Wert und schließt das Dialogfeld automatisch, wenn der Benutzer eine Schaltfläche auswählt (man muss die Hide-Methode nicht aufrufen). 6. Ist das folgende Codestück korrekt? Falls nicht, nennen Sie die Fehler. public property IsClicked as string Get return blnClicked end property
Wird nur das Get-Statement benutzt, muss die Eigenschaft als readonly markiert werden. Außerdem fehlt das End Get-Statement. 7. Welche zwei Mitglieder haben fast alle Standarddialogfeld-Steuerelemente gemeinsam? Nennen Sie die Ausnahme(n). ShowHelp und Reset. Diese Mitglieder sind Bestandteil aller Standarddialogfeld-Steuerelemente, mit Ausnahme des PrintPreviewDialog-Steuerelements.
8. Schreiben Sie eine Filterzeichenfolge für ein OpenFileDialog-Steuerelement, das Textdateien (*.txt), Bilddateien (*.gif) und Alle Dateien (*.*) anzeigt. "Text Dateien (*.txt)|*.txt|GIF-Bilder (*.gif)|*.gif|Alle Dateien (*.*)|*.*"
9. Nennen Sie die Haupteigenschaften (also die Eigenschaften, die Sie am meisten interessieren, wenn der Benutzer eine Auswahl trifft) für die Steuerelemente OpenFileDialog, SaveFileDialog, ColorDialog und FontDialog. Jeweils Filename, Filename, Color und Font.
748
Antworten auf die Quizfragen und Übungen
Übung Erstellen Sie in C# eine voll funktionsfähige Anwendung mit Menüs, welche alle Standarddialogfeld-Steuerelemente verwendet. Setzen Sie auch ein nichtmodales Dialogfeld ein, um kontextabhängige Hilfeinformationen anzuzeigen. Wenn sich ein Benutzer von einem Menüelement zum nächsten bewegt, sollte sich der Inhalt dieses Dialogfeldes ändern. (Tipp: Verwenden Sie das MenuItem.Select-Ereignis, um festzustellen, über welchem Menüelement sich die Maus gerade befindet.) 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37:
using using using using
System; System.Windows.Forms; System.Drawing; System.Drawing.Printing;
namespace TYWinforms.Day7 { public class Exercise1 : Form { PageSettings objPageSettings = new PageSettings(); MainMenu mnuMain = new MainMenu(); MenuItem mniFile = new MenuItem("File"); MenuItem mniOpen = new MenuItem("Open"); MenuItem mniSave = new MenuItem("Save"); MenuItem mniPageSetup = new MenuItem("Page Setup"); MenuItem mniPrintPreview = new MenuItem("Print Preview"); MenuItem mniPrint = new MenuItem("Print"); MenuItem mniEdit = new MenuItem("Edit"); MenuItem mniFont = new MenuItem("Font"); MenuItem mniColor = new MenuItem("Color"); HelpDialog dlgHelp = new HelpDialog(); public Exercise1() { mnuMain.MenuItems.Add(mniFile); mnuMain.MenuItems.Add(mniEdit); mniFile.MenuItems.Add(mniOpen); mniFile.MenuItems.Add(mniSave); mniFile.MenuItems.Add("-"); mniFile.MenuItems.Add(mniPageSetup); mniFile.MenuItems.Add(mniPrintPreview); mniFile.MenuItems.Add(mniPrint); mniEdit.MenuItems.Add(mniFont); mniEdit.MenuItems.Add(mniColor); mniOpen.Click += new EventHandler(this.FileMenuClick); mniSave.Click += new EventHandler(this.FileMenuClick);
749
Anhang A
38: mniPageSetup.Click += new EventHandler(this.FileMenuClick); 39: mniPrintPreview.Click += new EventHandler(this.FileMenuClick); 40: mniPrint.Click += new EventHandler(this.FileMenuClick); 41: mniFont.Click += new EventHandler(this.EditMenuClick); 42: mniColor.Click += new EventHandler(this.EditMenuClick); 43: 44: mniOpen.Select += new EventHandler(this.DisplayHelp); 45: mniSave.Select += new EventHandler(this.DisplayHelp); 46: mniPageSetup.Select += new EventHandler(this.DisplayHelp); 47: mniPrintPreview.Select += new EventHandler(this.DisplayHelp); 48: mniPrint.Select += new EventHandler(this.DisplayHelp); 49: mniFont.Select += new EventHandler(this.DisplayHelp); 50: mniColor.Select += new EventHandler(this.DisplayHelp); 51: 52: this.Menu = mnuMain; 53: this.Text = "Exercise 1"; 54: } 55: 56: public void FileMenuClick(Object Sender, EventArgs e) { 57: MenuItem mniTemp = (MenuItem)Sender; 58: 59: switch (mniTemp.Text) { 60: case "Open": 61: OpenFileDialog dlgOpen = new OpenFileDialog(); 62: if (dlgOpen.ShowDialog() == DialogResult.OK) { 63: //open file 64: } 65: break; 66: case "Save": 67: SaveFileDialog dlgSave = new SaveFileDialog(); 68: if (dlgSave.ShowDialog() == DialogResult.OK) { 69: //save file 70: } 71: break; 72: case "Page Setup": 73: PageSetupDialog dlgPageSetup = new PageSetupDialog(); 74: dlgPageSetup.PageSettings = objPageSettings; 75: dlgPageSetup.ShowDialog(); 76: break; 77: case "Print Preview": 78: PrintPreviewDialog dlgPrintPreview = new PrintPreviewDialog(); 79: dlgPrintPreview.Document = new PrintDocument(); 80: dlgPrintPreview.ShowDialog(); 81: break; 82: case "Print":
750
Antworten auf die Quizfragen und Übungen
83: 84: 85: 86: 87: 88: 89: 90: 91: 92: 93: 94: 95: 96: 97: 98: 99: 100: 101: 102: 103: 104: 105: 106: 107: 108: 109: 110: 111: 112: 113: 114: 115: 116: 117: 118: 119: 120: 121: 122: 123: 124: 125: 126: 127: 128:
PrintDialog dlgPrint = new PrintDialog(); dlgPrint.PrinterSettings = new PrinterSettings(); if (dlgPrint.ShowDialog() == DialogResult.OK) { //print } break; } } public void EditMenuClick(Object Sender, EventArgs e) { MenuItem mniTemp = (MenuItem)Sender; switch (mniTemp.Text) { case "Font": FontDialog dlgFont = new FontDialog(); if (dlgFont.ShowDialog() == DialogResult.OK) { this.Font = dlgFont.Font; } break; case "Color": ColorDialog dlgColor = new ColorDialog(); if (dlgColor.ShowDialog() == DialogResult.OK) { this.BackColor = dlgColor.Color; } break; } } public void DisplayHelp(Object Sender, EventArgs e) { MenuItem mniTemp = (MenuItem)Sender; if (!dlgHelp.Visible) { dlgHelp.Show(); } switch (mniTemp.Text) { case "Open": dlgHelp.HelpText = break; case "Save": dlgHelp.HelpText = break; case "Page Setup": dlgHelp.HelpText = break; case "Print Preview": dlgHelp.HelpText = break;
"Open a file";
"Save this file";
"Change page settings";
"Preview the document before printing";
751
Anhang A
129: case "Print": 130: dlgHelp.HelpText = "Print the current document"; 131: break; 132: case "Font": 133: dlgHelp.HelpText = "Change the current font face, color, size, etc"; 134: break; 135: case "Color": 136: dlgHelp.HelpText = "Change the background color of the application"; 137: break; 138: } 139: } 140: 141: public static void Main() { 142: Application.Run(new Exercise1()); 143: } 144: } 145: 146: public class HelpDialog : Form { 147: private Label lblMessage = new Label(); 148: 149: public String HelpText { 150: get { return lblMessage.Text; } 151: set { lblMessage.Text = value; } 152: } 153: 154: public HelpDialog() { 155: lblMessage.Size = new Size(100,100); 156: lblMessage.Text = "Help screen"; 157: 158: this.Controls.Add(lblMessage); 159: this.BackColor = Color.Yellow; 160: this.Size = new Size(100,100); 161: this.FormBorderStyle = FormBorderStyle.FixedToolWindow; 162: } 163: } 164: }
752
Antworten auf die Quizfragen und Übungen
Tag 8 Quiz 1. Schreiben Sie ein Statement, das einem Textfeld-Steuerelement namens tbOne eine neue Datenbindung hinzufügt. Binden Sie an die Text-Eigenschaft das Array arrOne. tbOne.DataBindings.Add("Text", arrOne, "");
2. Ist das folgende Statement für das Textfeld aus Frage 1 korrekt? tbOne.BindingContext.Position += 1;
Nein, Sie müssen den bestimmten Bindungskontext so festlegen, dass er sich laufend erhöht: tbOne.BindingContext[arrOne].Position += 1;
3. Wahr oder falsch? Man kann Daten an ein Form-Objekt binden. Wahr. Solche Daten anzubinden, ist häufig eine einfache Methode, eine Anwendung mit Daten zu verknüpfen. 4. Welches Ereignis wird oft verwendet, um die HitTest-Methode aufzurufen, und aus welchem Grund? Dafür wird oft das MouseDown-Ereignis benutzt. Es liefert spezifische Koordinaten, wo sich der Mausklick ereignete. 5. Wie würde die Anzeige der Zahl "458.34e+04" aussehen, wenn man die Formatzeichenfolge "d3" darauf anwendet? Das ist eine Fangfrage. Die Anzeige würde "4583400" lauten. Wenn man einen Präzisionswert in der Formatzeichenfolge mit weniger Ziffern als im Originalwert angibt, wird die Genauigkeit ignoriert. 6. Welcher Eigenschaft des DataGrid-Steuerelements fügen Sie DataGridTableStyleObjekte hinzu? Der TableStyles-Eigenschaft. 7. Welcher Eigenschaft des DataGrid-Steuerelements fügen Sie DataGridColumnStyleObjekte hinzu? Schon wieder eine Fangfrage. Man fügt keine DataGridColumnStyle-Objekte einem DataGrid-Steuerelement hinzu, sondern fügt sie der GridColumnStyles-Eigenschaft des DataGridTableStyle-Objekts hinzu.
753
Anhang A
8. Angenommen, Sie haben eine DataTable namens dtData mit zwei Spalten namens ColA und ColB. Erzeugen Sie eine neue, leere Zeile. DataRow myRow myRow = dtRow.NewRow(); dtData.Rows.Add(myRow);
9. Erzeugen Sie die ColA-Spalte der in Frage 8 erwähnten DataTable als einen Integer-Typ. DataColumn colA = new DataColumn(); colA.DataType = System.Type.GetType("System.Int32"); ColA.ColumnName = "ColA"; dtData.Columns.Add(colA);
Übung Erstellen Sie eine DataGrid und DataTable verwendende Anwendung, in der der Benutzer seine Kontoführungsinformationen – wie in Quicken oder Microsoft Money – eingeben kann. Gestatten Sie nicht, dass das DataGrid manuell editiert wird, sondern stellen Sie vielmehr dem Benutzer andere Steuerelemente für die Eingabe der Informationen bereit, die dann im DataGrid beim Bestätigen bzw. Absenden angezeigt werden. Um dieses Beispiel einfach zu halten, gestatten Sie momentan nur Abbuchungen. 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23:
754
using using using using
System; System.Windows.Forms; System.Drawing; System.Data;
namespace TYWinforms.Day8 { public class Exercise1 : Form { private DataGrid dgMoney = new DataGrid(); private DataTable dtMoney = new DataTable("Money"); private private private private private private private private private private
Panel pnlAdd = new Panel(); TextBox tbNum = new TextBox(); TextBox tbDate = new TextBox(); TextBox tbPayee = new TextBox(); TextBox tbAmount = new TextBox(); Label lblNum = new Label(); Label lblDate = new Label(); Label lblPayee = new Label(); Label lblAmount = new Label(); Button btSubmit = new Button();
public Exercise1() { //create columns
Antworten auf die Quizfragen und Übungen
24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69:
DataColumn colNum = new DataColumn(); colNum.DataType = System.Type.GetType ("System.Int32"); colNum.ColumnName = "Check Number"; dtMoney.Columns.Add(colNum); DataColumn colAmount = new DataColumn(); colAmount.DataType = System.Type.GetType ("System.Double"); colAmount.ColumnName = "Amount"; dtMoney.Columns.Add(colAmount); DataColumn colPayee = new DataColumn(); colPayee.DataType = System.Type.GetType ("System.String"); colPayee.ColumnName = "Payee"; dtMoney.Columns.Add(colPayee); DataColumn colDate = new DataColumn(); colDate.DataType = System.Type.GetType ("System.DateTime"); colDate.ColumnName = "Date"; dtMoney.Columns.Add(colDate); //create styles DataGridTableStyle dgtStyle = new DataGridTableStyle(); dgtStyle.MappingName = "Money"; DataGridTextBoxColumn dgcStyle = new DataGridTextBoxColumn(); dgcStyle.MappingName = "Check Number"; dgcStyle.ReadOnly = true; dgcStyle.HeaderText = "Check No."; dgcStyle.Width = 100; dgtStyle.GridColumnStyles.Add(dgcStyle); dgcStyle = new DataGridTextBoxColumn(); dgcStyle.MappingName = "Date"; dgcStyle.ReadOnly = true; dgcStyle.HeaderText = "Date"; dgcStyle.Width = 100; dgtStyle.GridColumnStyles.Add(dgcStyle); dgcStyle = new DataGridTextBoxColumn(); dgcStyle.MappingName = "Payee"; dgcStyle.ReadOnly = true; dgcStyle.HeaderText = "Payee"; dgcStyle.Width = 452; dgtStyle.GridColumnStyles.Add(dgcStyle); dgcStyle = new DataGridTextBoxColumn();
755
Anhang A
70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80: 81: 82: 83: 84: 85: 86: 87: 88: 89: 90: 91: 92: 93: 94: 95: 96: 97: 98: 99: 100: 101: 102: 103: 104: 105: 106: 107: 108: 109: 110: 111: 112: 113: 114: 115:
756
dgcStyle.MappingName = "Amount"; dgcStyle.ReadOnly = true; dgcStyle.HeaderText = "Amount"; dgcStyle.Width = 100; dgcStyle.Format = "c"; dgtStyle.GridColumnStyles.Add(dgcStyle); //create add form lblNum.Text = "Check No."; lblNum.Location = new Point(10,250); tbNum.Location = new Point(75,245); lblDate.Text = "Date"; lblDate.Location = new Point(600,250); tbDate.Location = new Point(675,245); lblPayee.Text = "Pay To: "; lblPayee.Location = new Point(10,280); tbPayee.Location = new Point(75,275); tbPayee.Width = 400; lblAmount.Text = "Amount"; lblAmount.Location = new Point(600,280); tbAmount.Location = new Point(675,275); btSubmit.Text = "Enter"; btSubmit.Location = new Point(675,310); btSubmit.Click += new EventHandler (this.AddRecord); pnlAdd.Size = new Size(800,500); pnlAdd.Dock = DockStyle.Bottom; pnlAdd.BackColor = Color.Tan; pnlAdd.Controls.Add(tbNum); pnlAdd.Controls.Add(tbDate); pnlAdd.Controls.Add(tbPayee); pnlAdd.Controls.Add(tbAmount); pnlAdd.Controls.Add(lblNum); pnlAdd.Controls.Add(lblDate); pnlAdd.Controls.Add(lblPayee); pnlAdd.Controls.Add(lblAmount); pnlAdd.Controls.Add(btSubmit); dgMoney.Dock = DockStyle.Top; dgMoney.Size = new Size(800,300); dgMoney.CaptionText = "Checkbook Register"; dgMoney.BackgroundColor = Color.White;
Antworten auf die Quizfragen und Übungen
116: dgMoney.TableStyles.Add(dgtStyle); 117: dgMoney.DataSource = dtMoney; 118: 119: this.Size = new Size(800,600); 120: this.Text = ".NET Check Book Register"; 121: this.Controls.Add(dgMoney); 122: this.Controls.Add(pnlAdd); 123: } 124: 125: private void AddRecord(Object Sender, EventArgs e) { 126: if (tbPayee.Text == "" | tbAmount.Text == "") { 127: MessageBox.Show("You forgot to enter a payee or amount.","Check Register"); 128: } else { 129: DataRow newRow = dtMoney.NewRow(); 130: 131: newRow[0] = Convert.ToInt32(tbNum.Text); 132: newRow[1] = Convert.ToDouble(tbAmount.Text); 133: newRow[2] = tbPayee.Text; 134: newRow[3] = Convert.ToDateTime(tbDate.Text); 135: 136: dtMoney.Rows.Add(newRow); 137: 138: tbNum.Text = ""; 139: tbDate.Text = ""; 140: tbAmount.Text = ""; 141: tbPayee.Text = ""; 142: } 143: } 144: 145: public static void Main() { 146: Application.Run(new Exercise1()); 147: } 148: } 149: }
757
Anhang A
Tag 9 Quiz 1. Schreiben Sie ein SELECT-Statement, das aus der tblUsers-Tabelle nur solche Datensätze holt, in denen der Wert für das Feld UserID zwischen 5 und 10 liegt. Sie können dieses Statement auf zwei verschiedene Arten formulieren: SELECT * FROM tblUsers WHERE UserID < 11 and UserID > 4
oder SELECT * FROM tblUsers WHERE UserID BETWEEN 5 AND 10
2. Welche Parameter fordert das SqlCommandBuilder-Objekt für seinen Konstruktor? Das SqlCommandBuilder-Objekt erfordert eine Instanz eines SqlDataAdapter-Objekts. 3. Ein SqlCommandBuilder erzeugt SQL-Befehle nur dann automatisch, wenn ein Primärschlüssel existiert. Warum? Die erzeugten Befehle müssen eine WHERE-Klausel enthalten, die die Zahl der geänderten Datensätze auf die richtigen eingrenzt. Die Verwendung eines Primärschlüssels ist ein Weg, um sicherzustellen, dass nur die richtigen Datensätze geändert werden. Das folgende Statement könnte mehr als einen Datensatz verändern: DELETE FROM tblUsers WHERE NACHNAME = 'Payne'
Statt dessen will man sicherstellen, dass nur ein einziger Datensatz betroffen ist – der Primärschlüssel, der ja für jeden Datensatz eindeutig sein muss, sorgt dafür. Der folgende Befehl wird garantiert nur einen einzigen Datensatz löschen: DELETE FROM tblUsers WHERE UserID = 1
4. Wahr oder falsch? Meist können Sie die Vorsilbe Sql durch OleDb ersetzen, um die Objekte im OLE DB-Provider nutzen zu können. Wahr. Beispielsweise wird SqlDataAdapter zu OleDbDataAdapter und aus SqlConnection wird OleDbConnection. 5. Ist es ausreichend, die DataSource-Eigenschaft eines DataGrid-Steuerelements auf ein gefülltes DataSet zu setzen? Nein. Man muss außerdem die DataMember-Eigenschaft auf die DataTable einstellen, die die anzuzeigenden Informationen enthält. 6. Schreiben Sie für ein gegebenes SqlCommand-Objekt namens objCmd ein Statement, das einen Parameter namens @Geburtsdatum mit einem Wert von 1/7/01 hinzufügt.
758
Antworten auf die Quizfragen und Übungen
Sie können das Statement auf zweierlei Weise formulieren: objCmd.Parameters.Add("@Geburtsdatum", SqlDbType.varChar, 10).Value = "1/7/ 01";
oder: objCmd.Parameters.Add("@Geburtsdatum", SqlDbType.DateTime, 10).Value = "1/7/ 01";
7. Nennen Sie die Methode, die veranlasst, dass Änderungen sofort in ein DataSet geschrieben werden. Dies ist die EndCurrentEdit-Methode des BindingContext-Objekts.
Übung Erstellen Sie eine Anwendung, mit der der Benutzer SQL-Statements in ein Textfeld eingeben kann. Sollte das Statement Ergebnisse liefern, legen Sie sie in einem DataSet ab und gestatten Sie dessen Bearbeitung. Kümmern Sie sich nicht darum, ob die Abfrage im richtigen Format eingegeben wird; Fehlerprüfungen werden erst an Tag 21 durchgenommen. 1: using System; 2: using System.Windows.Forms; 3: using System.Drawing; 4: using System.Data; 5: using System.Data.SqlClient; 6: 7: namespace TYWinforms.Day9 { 8: public class Exercise1 : Form { 9: private DataGrid dgData = new DataGrid(); 10: private TextBox tbCommand = new TextBox(); 11: private Button btExecute = new Button(); 12: private DataSet objDS = new DataSet(); 13: private String strConn; 14: 15: public Exercise1() { 16: strConn = "Initial Catalog=TYWinforms;DataSource=localhost;User ID=sa"; 17: 18: tbCommand.Location = new Point(150,75); 19: tbCommand.Multiline = true; 20: tbCommand.Height = 50; 21: tbCommand.Width = 300; 22: 23: btExecute.Location = new Point(475,75); 24: btExecute.Text = "Execute!"; 25: btExecute.Click += new EventHandler (this.Execute); 26:
759
Anhang A
27: dgData.Dock = DockStyle.Bottom; 28: dgData.Size = new Size(800,400); 29: dgData.Enabled = false; 30: dgData.CurrentCellChanged += new EventHandler(this.UpdateData); 31: 32: this.Size = new Size(800,600); 33: this.Text = "Exercise 1"; 34: this.Controls.Add(dgData); 35: this.Controls.Add(tbCommand); 36: this.Controls.Add(btExecute); 37: } 38: 39: private void Execute(Object Sender, EventArgs e) { 40: if (tbCommand.Text != "") { 41: SqlConnection objConn = new SqlConnection(strConn); 42: 43: SqlDataAdapter objCmd = new SqlDataAdapter(tbCommand.Text, objConn); 44: objDS.Clear(); 45: int intRows = objCmd.Fill(objDS, "Query"); 46: 47: if (intRows > 0) { 48: dgData.ResetBindings(); 49: dgData.DataSource = objDS; 50: dgData.DataMember = "Query"; 51: dgData.Enabled = true; 52: } else { 53: dgData.Enabled = false; 54: } 55: } 56: } 57: 58: public void UpdateData(Object Sender, EventArgs e) { 59: SqlConnection objConn = new SqlConnection(strConn); 60: SqlDataAdapter objCmd = new SqlDataAdapter(tbCommand.Text, objConn); 61: SqlCommandBuilder objBuilder = new SqlCommandBuilder(objCmd); 62: 63: objCmd.Update(objDS, "Query"); 64: } 65: 66: public static void Main() { 67: Application.Run(new Exercise1()); 68: } 69: } 70: }
760
Antworten auf die Quizfragen und Übungen
Tag 10 Quiz 1. Welche Eigenschaft müssen Sie im übergeordneten MDI-Formular einstellen, um es zu einer MDI-Anwendung zu machen? Man muss IsMdiContainer auf true setzen. 2. Welche Eigenschaft müssen Sie im untergeordneten MDI-Dokument einstellen, um es zum Bestandteil einer MDI-Anwendung zu machen? Man setzt die MdiParent-Eigenschaft des untergeordneten Dokuments auf das übergeordnete Formular. Das muss im übergeordneten Formular selbst erfolgen. 3. Über welche drei Werte verfügt die Aufzählung MdiLayout? MdiLayout.Cascade, MdiLayout.TileHorizontal und MdiLayout.TileVertical.
4. Wahr oder falsch? Sie müssen erst die Show-Methode aufrufen, bevor Sie ein untergeordnetes MDI-Formular anzeigen können, wenn es im Konstruktor seines übergeordneten Formulars erzeugt wird. Falsch. Man braucht die Show-Methode nur aufzurufen, wenn das untergeordnete Dokument außerhalb des Konstruktors angelegt wird. 5. Der folgende Code veranlasst das TextBox-Steuerelement tbText nicht dazu, das gesamte Formular auszufüllen: TextBox tbText = new TextBox(); tbText.Dock = DockStyle.Fill;
Warum nicht? Damit sich ein TextBox-Steuerelement in der Höhe mehr als über den Vorgabewert ausdehnt, muss man auch die Multiline-Eigenschaft auf true setzen: tbText.Multiline = true;
6. Wie findet man heraus, ob ein untergeordnetes MDI-Fenster aktiv ist? Man bewertet die ActiveMdiChild-Eigenschaft und findet heraus, ob sie mit null (Nothing in VB .NET) bewertet wird. 7. Sie haben drei Menüelemente in einem untergeordneten Dokument erzeugt, aber sie werden im übergeordneten Dokument nicht richtig angezeigt. Nennen Sie drei Dinge, die man zur Behebung dieses Problems prüfen sollte.
761
Anhang A
Setzt man einen anderen MenuMerge-Wert als Remove ein, sollte man dafür sorgen, dass die Menüelemente sowohl in der übergeordneten als auch in den untergeordneten Klassen zugewiesen sind. Man muss dafür sorgen, dass die MergeOrder-Werte übereinstimmen (die Menüs, die sich verbinden/miteinander verschmelzen/ersetzen sollen, haben gleiche MergeOrderWerte). Prüfen Sie besser doppelt, ob Sie die richtigen MenuMerge-Werte benutzen. Sie könnten aus Versehen Remove in der untergeordneten Klasse benutzen, im Glauben, dass dies das übergeordnete Menüelement entfernt, doch in Wahrheit entfernt dies das untergeordnete Menüelement. 8. Wahr oder falsch? Der MergeOrder-Wert muss sich schrittweise erhöhen lassen; Sie können keine Werte in der Reihenfolge überspringen. Falsch. Der tatsächliche Wert von MergeOrder ist gleichgültig, wenn man Menüelemente anordnet; nur auf relative Werte kommt es an.
Übung Erstellen Sie eine MDI-Version der gestrigen Anwendung für die Ausführung von SQL-Statements. Erzeugen Sie ein benutzerdefiniertes Dialogfeld, das ein Textfeld anzeigt, in dem der Benutzer seine Abfrage eingeben kann. Exercise1.cs: 1: using System; 2: using System.Windows.Forms; 3: using System.Drawing; 4: 5: namespace TYWinforms.Day10 { 6: public class Exercise1 : Form { 7: private MainMenu mnuMain = new MainMenu(); 8: private int intCounter = 0; 9: 10: public Exercise1() { 11: MenuItem miQuery = mnuMain.MenuItems.Add("Query"); 12: MenuItem miNew = miQuery.MenuItems.Add("New"); 13: miNew.Click += new EventHandler(this.HandleMenu); 14: 15: MenuItem miWindow = mnuMain.MenuItems.Add("&Window"); 16: miWindow.MenuItems.Add("Cascade", new EventHandler(this.ArrangeWindows)); 17: miWindow.MenuItems.Add("Tile Horizontal", new EventHandler(this.ArrangeWindows));
762
Antworten auf die Quizfragen und Übungen
18: miWindow.MenuItems.Add("Tile Vertical", new EventHandler(this.ArrangeWindows)); 19: miWindow.MdiList = true; 20: 21: QueryDialog dlgQuery = new QueryDialog(); 22: this.AddOwnedForm(dlgQuery); 23: dlgQuery.TopMost = true; 24: dlgQuery.Show(); 25: 26: this.Size = new Size(800,600); 27: this.Text = "Exercise 1"; 28: this.Menu = mnuMain; 29: this.IsMdiContainer = true; 30: } 31: 32: private void ArrangeWindows(Object Sender, EventArgs e) { 33: MenuItem miTemp = (MenuItem)Sender; 34: 35: switch (miTemp.Text) { 36: case "Cascade": 37: this.LayoutMdi(MdiLayout.Cascade); 38: break; 39: case "Tile Horizontal": 40: this.LayoutMdi(MdiLayout.TileHorizontal); 41: break; 42: case "Tile Vertical": 43: this.LayoutMdi(MdiLayout.TileVertical); 44: break; 45: } 46: } 47: 48: private void HandleMenu(Object Sender, EventArgs e) { 49: intCounter++; 50: 51: DataDocument doc = new DataDocument ("Query Results " + intCounter.ToString()); 52: doc.MdiParent = this; 53: doc.Show(); 54: } 55: 56: public static void Main() { 57: Application.Run(new Exercise1()); 58: } 59: } 60: }
763
Anhang A
QueryDialog.cs: 1: using System; 2: using System.Windows.Forms; 3: using System.Drawing; 4: 5: namespace TYWinforms.Day10 { 6: public class QueryDialog : Form { 7: private TextBox tbCommand = new TextBox(); 8: private Button btExecute = new Button(); 9: 10: public QueryDialog() { 11: tbCommand.Location = new Point(10,10); 12: tbCommand.Multiline = true; 13: tbCommand.Height = 50; 14: tbCommand.Width = 250; 15: 16: btExecute.Location = new Point(10,75); 17: btExecute.Text = "Execute!"; 18: btExecute.Click += new EventHandler(this.Execute); 19: 20: this.Size = new Size(300,150); 21: this.Text = "Query Executor"; 22: this.Controls.Add(tbCommand); 23: this.Controls.Add(btExecute); 24: } 25: 26: private void Execute(Object Sender, EventArgs e) { 27: if (tbCommand.Text != "") { 28: if (this.Owner.ActiveMdiChild != null) { 29: 30: ((DataDocument)this.Owner.ActiveMdiChild).Execute(tbCommand.Text); 31: } 32: } 33: } 34: } 35: }
DataDocument.cs: 1: 2: 3: 4: 5: 6: 7:
764
using using using using using
System; System.Windows.Forms; System.Drawing; System.Data; System.Data.SqlClient;
namespace TYWinforms.Day10 {
Antworten auf die Quizfragen und Übungen
8: public class DataDocument : Form { 9: private DataGrid dgData = new DataGrid(); 10: private DataSet objDS = new DataSet(); 11: private String strConn; 12: private String strQuery; 13: 14: public DataDocument(string strName) { 15: strConn = "Initial Catalog=TYWinforms;DataSource=localhost; User ID=sa"; 16: 17: dgData.Dock = DockStyle.Fill; 18: dgData.Enabled = false; 19: dgData.CurrentCellChanged += new EventHandler(this.UpdateData); 20: 21: this.WindowState = FormWindowState.Maximized; 22: this.Text = strName; 23: this.Controls.Add(dgData); 24: } 25: 26: public void Execute(string strQuery) { 27: this.strQuery = strQuery; 28: 29: SqlConnection objConn = new SqlConnection(strConn); 30: 31: SqlDataAdapter objCmd = new SqlDataAdapter(strQuery, objConn); 32: objDS.Clear(); 33: int intRows = objCmd.Fill(objDS, "Query"); 34: 35: if (intRows > 0) { 36: dgData.ResetBindings(); 37: dgData.DataSource = objDS; 38: dgData.DataMember = "Query"; 39: dgData.Enabled = true; 40: } else { 41: dgData.Enabled = false; 42: } 43: } 44: 45: public void UpdateData(Object Sender, EventArgs e) { 46: SqlConnection objConn = new SqlConnection(strConn); 47: SqlDataAdapter objCmd = new SqlDataAdapter(strQuery, objConn); 48: SqlCommandBuilder objBuilder = new SqlCommandBuilder(objCmd); 49: 50: objCmd.Update(objDS, "Query"); 51: } 52: } 53: }
765
Anhang A
Tag 11 Quiz 1. Worin besteht der Unterschied zwischen den Read- und Peek-Methoden? Sowohl die Read- als auch die Peek-Methoden liefern das nächste Zeichen in einem Strom, doch Peek bewegt dabei nicht den Zeiger im Strom. Daher liefern nachfolgende Peek-Aufrufe stets das gleiche Zeichen, wohingegen Read-Aufrufe den Zeiger bewegen und nachfolgende Zeichen liefern. 2. Was ist eine rekursive Funktion und wann erweist sie sich als hilfreich? Eine rekursive Funktion ruft sich selbst immer wieder neu auf. Solch eine Funktion setzt man ein, wenn man nicht im Voraus weiß, wie oft etwas ausgeführt werden muss, beispielsweise in einer Verzeichnisstruktur. 3. Worin besteht der Unterschied zwischen synchroner und asynchroner Arbeitsweise? Synchrone Arbeitsweise erfordert, dass alles andere in bestimmter Reihenfolge und synchron ausgeführt wird, wohingegen bei asynchroner Arbeitsweise die Ausführung aufgeteilt werden kann, um so gleichzeitig ausgeführt zu werden. Das erspart Zeit. 4. Zu welchem Namensraum gehört die Druckfunktionalität? System.Drawing.Printing.
5. Welche sind die vier Ereignisse des FileSystemWatcher-Objekts? Created, Changed, Renamed und Deleted.
6. Welche sind die Werte der FileMode-Aufzählung ? Append, Create, CreateNew, Open, OpenOrCreate und Truncate.
Übung Modifizieren Sie Listing 11.1 so, dass die TreeView-Knoten jedes Mal dynamisch erstellt werden, wenn ein Benutzer einen Knoten expandiert, anstatt alle während der Initialisierungsphase der Anwendung erstellen zu lassen. Fügen Sie außerdem eine benutzerdefinierte Druckfunktion hinzu, um die sichtbaren Inhalte des TreeView-Steuerelements drucken zu können. (Tipp: Sie müssen die Inhalte der Strukturansicht erst in einer passenden Variablen sammeln, um sie drucken zu können.) 1: 2: 3:
766
using System; using System.Windows.Forms; using System.Drawing;
Antworten auf die Quizfragen und Übungen
4: using System.IO; 5: using System.Drawing.Printing; 6: 7: namespace TYWinforms.Day11 { 8: public class Exercise1 : Form { 9: private TreeView tvFiles = new TreeView(); 10: private Label lblInfo = new Label(); 11: private MainMenu mnuMain = new MainMenu(); 12: private String strList = ""; 13: private StringReader objPrintReader; 14: 15: public Exercise1() { 16: tvFiles.Dock = DockStyle.Left; 17: tvFiles.Width = 200; 18: tvFiles.AfterSelect += new TreeViewEventHandler(this.DisplayInfo); 19: tvFiles.BeforeExpand += new TreeViewCancelEventHandler(this.ExpandThread); 20: 21: lblInfo.Size = new Size(150,150); 22: lblInfo.Location = new Point(210,10); 23: lblInfo.Text = "Select a file or\ndirectory"; 24: 25: PopulateList("c:\\", tvFiles.Nodes.Add("c:\\")); 26: 27: MenuItem miFile = new MenuItem("File"); 28: MenuItem miPrint = new MenuItem("Print"); 29: miPrint.Click += new EventHandler (this.PrintClicked); 30: 31: mnuMain.MenuItems.Add(miFile); 32: miFile.MenuItems.Add(miPrint); 33: 34: this.Menu = mnuMain; 35: this.Text = "Exercise 1"; 36: this.Size = new Size(800,600); 37: this.Controls.Add(tvFiles); 38: this.Controls.Add(lblInfo); 39: } 40: 41: private void PopulateList(String strPath, TreeNode currNode) { 42: DirectoryInfo dir = new DirectoryInfo(strPath); 43: TreeNode nodeSubDir; 44: 45: foreach (DirectoryInfo d in dir.GetDirectories()) { 46: nodeSubDir = currNode.Nodes.Add(d.Name); 47: nodeSubDir.Nodes.Add(""); 48: }
767
Anhang A
49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80: 81: 82: 83: 84: 85: 86: 87: 88: 89: 90: 91: 92: 93: 94:
768
foreach (FileInfo f in dir.GetFiles("*.*")) { currNode.Nodes.Add(f.Name); } } private void ExpandThread(Object Sender, TreeViewCancelEventArgs e) { if (e.Node.Nodes[0].Text == "") { TreeNode tempNode = e.Node; String strFullName = e.Node.Text; while (tempNode.Parent != null) { strFullName = tempNode.Parent.Text + "\\" + strFullName; tempNode = tempNode.Parent; } e.Node.Nodes[0].Remove(); PopulateList(strFullName, e.Node); } } private void DisplayInfo(Object Sender, TreeViewEventArgs e) { TreeNode tempNode = e.Node; String strFullName = tempNode.Text; while (tempNode.Parent != null) { strFullName = tempNode.Parent.Text + "\\" + strFullName; tempNode = tempNode.Parent; } if (File.Exists(strFullName)) { FileInfo obj = new FileInfo(strFullName); lblInfo.Text = "Name: " + obj.Name; lblInfo.Text += "\nSize: " + obj.Length; lblInfo.Text += "\nAccessed: " + obj.LastAccessTime.ToString(); } else { DirectoryInfo obj = new DirectoryInfo (strFullName); lblInfo.Text = "Name: " + obj.Name; lblInfo.Text += "\nAttributes: " + obj.Attributes.ToString(); lblInfo.Text += "\nAccessed: " + obj.LastAccessTime.ToString(); } } private void PrintClicked(Object Sender, EventArgs e) { //Assemble string of nodes strList = "";
Antworten auf die Quizfragen und Übungen
95: TreeNode nodeTemp = tvFiles.TopNode; 96: 97: for (int i = 0; i < tvFiles.GetNodeCount(true); i++) { 98: /*pad the string with spaces for easier 99: reading */ 100: strList += "".PadRight(GetLevel(nodeTemp)*2) + nodeTemp.Text + "\n"; 101: 102: nodeTemp = nodeTemp.NextVisibleNode; 103: if (nodeTemp == null) break; 104: 105: nodeTemp.EnsureVisible(); 106: } 107: 108: PrintDocument pd = new PrintDocument(); 109: pd.PrintPage += new PrintPageEventHandler (this.pd_PrintPage); 110: objPrintReader = new StringReader(strList); 111: pd.Print(); 112: } 113: 114: /* Returns an integer indicating how many levels deep 115: the specified node is in the treeview */ 116: private int GetLevel(TreeNode objNode) { 117: int intLevel = 0; 118: TreeNode nodeTemp = objNode; 119: 120: while (nodeTemp.Parent != null) { 121: intLevel++; 122: nodeTemp = nodeTemp.Parent; 123: } 124: 125: return intLevel; 126: } 127: 128: private void pd_PrintPage(Object Sender, PrintPageEventArgs e) { 129: Font fntPrint = this.Font; 130: int count = 0; 131: float yPos = 0; 132: float lpp = e.MarginBounds.Height / fntPrint.GetHeight(e.Graphics); 133: float fltTopMargin = e.MarginBounds.Top; 134: float fltLeftMargin = e.MarginBounds.Left; 135: String strLine = null; 136: 137: while (count < lpp && ((strLine = objPrintReader. ReadLine()) != null)) {
769
Anhang A
138: yPos = fltTopMargin + (count * fntPrint.GetHeight(e.Graphics)); 139: 140: e.Graphics.DrawString(strLine, fntPrint, Brushes.Black, fltLeftMargin, yPos, new StringFormat()); 141: 142: count++; 143: } 144: 145: if (strLine != null) { 146: e.HasMorePages = true; 147: } else { 148: e.HasMorePages = false; 149: } 150: } 151: 152: public static void Main() { 153: Application.Run(new Exercise1()); 154: } 155: } 156: }
Tag 12 Quiz 1. Wahr oder falsch? Um dem Server POST-Informationen zu schicken, verwendet man das WebResponse-Objekt. Falsch. Man benutzt eine WebRequest-Methode und ruft GetRequestStream auf, um POST-Daten zu senden.
2. Nennen Sie die vier Bestandteile eines URI. Das Protokoll, der Servername, der Verzeichnispfad und eine optionale Abfragezeichenfolge. 3. Welchen Typs muss der Inhalt sein, um Formularinformationen posten zu können? application/x-www-form-urlencoded
4. Was ist das HttpWebRequest-Objekt? Das HttpWebRequest-Objekt, ein Helfer des WebRequest-Objekts, gestattet feiner abgestimmte Kontrolle über eine HTTP-Anforderung. Man kann sein WebRequest-Objekt in ein HttpWebRequest umwandeln, um benutzerdefinierte HTTP-Header und andere HTTP-Einstellungen festzulegen.
770
Antworten auf die Quizfragen und Übungen
5. Welche sind die drei Typen der Windows-Authentifizierung? Basis-, Digest- und integrierte Windows-Authentifizierung.
Übung Erstellen Sie eine Anwendung, die eine Webseite (Ihrer Wahl) untersucht, deren Bilder parst (in HTML beginnen Grafiken mit dem Tag verwendet? Das <system.windows.forms>-Element bietet eine Möglichkeit, um JIT-Debugging zu aktivieren. 8. Was versteht man unter Camel-Schreibweise und wo ist sie erforderlich? Camel-Schreibweise (alternierende Groß-/Kleinschreibung) wird bei .config-XMLDateien eingesetzt. Camel-Schreibweise bedeutet, dass das erste Wort in einer Mehrwortverbindung klein geschrieben wird, wohingegen die Anfangsbuchstaben aller folgenden Wörter groß geschrieben werden. Einzelwörter schreibt man klein. Ein Beispiel: iAmCamelCased yippee goHome
9. Schreiben Sie eine einfache .config-Datei, die über einen -Abschnitt verfügt. Dieser Abschnitt sollte ein Element namens Color mit dem Wert Black enthalten.
Übung 1. Nehmen Sie drei weitere Ländereinstellungen in das Beispiel aus Listing 20.6 auf: Französisch (aus Frankreich), Dänisch und Griechisch. Kümmern Sie sich nicht um das Übersetzen der eigentlichen Ausdrücke, sondern ändern Sie sie nur so, dass sie die jeweils aktuelle Kultur widerspiegeln. (In der .NET Framework-Dokumentation zur CultureInfo-Klasse finden Sie Angaben zu den Kulturcodes für diese Sprachen.) 2. Erstellen Sie eine .config-Datei, die die jeweils zu verwendende Kultur von Übung 1 angibt. So müssen Sie nicht Ihren Quellcode verändern und die Anwendung jedes Mal kompilieren, wenn Sie eine neue Kultureinstellung testen wollen. (Tipp: Nutzen Sie den -Abschnitt.)
803
Anhang A
Antwort 1: Sie können die Antwort von der Sams Publishing Website herunterladen. In der Lösung werden Sie mehrere Verzeichnisse bemerken: /da, /el und /fr-FR. Jedes Verzeichnis enthält eine .txt-Datei mit den Lokalisierungsressourcen (z.B. Day20.frFR.txt), eine .resources-Datei, die aus der .txt-Datei mit Hilfe von resgen.exe erzeugt wird; und schließlich eine .dll-Datei, die eine kompilierte Satellitenassembly darstellt. Es gibt nur sehr wenig Code zu schreiben: Erstellen Sie einfach die .txtDateien, kompilieren Sie sie mit den heute besprochenen Befehlen und legen Sie sie in die notwendigen Verzeichnisse ab. Antwort 2: Die Lösung besteht in Ihrer neuen .config-Datei (die ebenfalls auf der SAMS-Website zur Verfügung steht). Ihre Anwendung sollte die ConfigurationSettings.AppSettings-Eigenschaft benutzen, um die Lokalisierungseinstellungen abzurufen.
Tag 21 Quiz 1. Wahr oder falsch? Ein catch-Block, der eine Ausnahme vom Typ IOException angibt, wird auch Ausnahmen vom Typ SystemException abfangen. Falsch. Es verhält sich genau andersherum. Ausnahmen vom Typ SystemException fangen IOException-Typen ab. 2. Ist der folgende Code richtig? Process [] objProcess = Process.GetProcessesByName("listing21.2.exe");
Nein. Wenn man Prozesse namentlich angibt, muss man die Erweiterung .exe im Prozessnamen weglassen. 3. Wie erzeugt man eine Programmdatenbankdatei? Wenn Sie Ihre Anwendung kompilieren, setzen Sie den Parameter /debug im Compilerbefehl ein. 4. Was versteht man unter einem Haltepunkt? Ein Haltepunkt ist eine vordefinierte Stelle im Code, an der die Ausführung für Debuggingzwecke angehalten wird. 5. Nennen Sie drei Eigenschaften der Exception-Klasse. Die Eigenschaften von Exception sind HelpLink, InnerException, Message, Source, StackTrace und TargetSite.
804
Antworten auf die Quizfragen und Übungen
6. Wie kann man eigene Fehlermeldungen auslösen? Man verwendet das Throw-Schlüsselwort und gibt dabei einen bestimmten Typ von Exception-Objekt an.
Übung Schreiben Sie eine Anwendung, die dem Windows Task-Manager gleicht. Sie sollte den Benutzer jeden ablaufenden Prozess über ein Listenfeld auswählen lassen. In Bezeichnungsfeldern sollten die Eigenschaften Id, MainModule, PrivateMemorySize und TotalProcessorTime des Prozesses sowie die Zahl der aktiven Threads zu sehen sein. Denken Sie auch daran, try-catch-Blöcke einzusetzen! 1: using System; 2: using System.Windows.Forms; 3: using System.Drawing; 4: using System.Diagnostics; 5: 6: namespace TYWinforms.Day21 { 7: public class Exercise : Form { 8: private ListBox lbxProcesses = new ListBox(); 9: 10: private Label lblCaptionId = new Label(); 11: private Label lblCaptionModule = new Label(); 12: private Label lblCaptionProcessor = new Label(); 13: private Label lblCaptionMemory = new Label(); 14: private Label lblCaptionThreadCount = new Label(); 15: 16: private Label lblId = new Label(); 17: private TextBox tbModule = new TextBox(); 18: private Label lblProcessor = new Label(); 19: private Label lblMemory = new Label(); 20: private Label lblThreadCount = new Label(); 21: 22: private System.Windows.Forms.Timer tmrProcess = new System.Windows.Forms.Timer(); 23: 24: public Exercise() { 25: lbxProcesses.Location = new Point(10,10); 26: lbxProcesses.Width = 280; 27: lbxProcesses.DisplayMember = "ProcessName"; 28: lbxProcesses.SelectedIndexChanged += new EventHandler(this.StartWatch); 29: 30: foreach (Process tmpProcess in Process.GetProcesses()) { 31: lbxProcesses.Items.Add(tmpProcess);
805
Anhang A
32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77:
806
} lblCaptionId.Location = new Point(10,120); lblCaptionId.Text = "PID: "; lblCaptionModule.Location = new Point(10,150); lblCaptionModule.Text = "Modules: "; lblCaptionProcessor.Location = new Point(10,180); lblCaptionProcessor.Text = "Processor Time: "; lblCaptionMemory.Location = new Point(10,210); lblCaptionMemory.Text = "Memory: "; lblCaptionThreadCount.Location = new Point(10,240); lblCaptionThreadCount.Text = "Thread Count: "; lblId.Location = new Point(110,120); lblId.Width = 150; tbModule.Location = new Point(110,150); tbModule.Multiline = true; tbModule.Size = new Size(500,25); tbModule.ReadOnly = true; tbModule.ScrollBars = ScrollBars.Vertical; lblProcessor.Location = new Point(110,180); lblProcessor.Width = 150; lblMemory.Location = new Point(110,210); lblMemory.Width = 150; lblThreadCount.Location = new Point(110,240); lblThreadCount.Width = 150; tmrProcess.Tick += new EventHandler(this.Update); tmrProcess.Interval = 500; this.Text = ".NET Profiler"; this.Width = 640; this.Controls.Add(lblCaptionId); this.Controls.Add(lblCaptionModule); this.Controls.Add(lblCaptionProcessor); this.Controls.Add(lblCaptionMemory); this.Controls.Add(lblCaptionThreadCount); this.Controls.Add(lbxProcesses); this.Controls.Add(lblId); this.Controls.Add(tbModule); this.Controls.Add(lblProcessor); this.Controls.Add(lblMemory); this.Controls.Add(lblThreadCount); } private void StartWatch(Object Sender, EventArgs e){
Antworten auf die Quizfragen und Übungen
78: 79: 80: 81: 82: 83: 84: 85: 86: 87: 88: 89: 90: 91: 92: 93: 94: 95: 96: 97: 98: 99: 100: 101: 102: 103: 104: 105: 106: 107: 108: 109: 110: 111:
//put things that don't change here try { Process tmpProcess = (Process)lbxProcesses.SelectedItem; tbModule.Text = ""; foreach (ProcessModule tmpModule in tmpProcess.Modules) { tbModule.Text += tmpModule.ModuleName + " "; } lblId.Text = tmpProcess.Id.ToString(); tmrProcess.Start(); } catch (Exception ex) { MessageBox.Show("That process is unwatchable"); } } private void Update(Object Sender, EventArgs e) { try { Process tmpProcess = (Process)lbxProcesses.SelectedItem; tmpProcess.Refresh(); lblMemory.Text = tmpProcess.PrivateMemorySize.ToString(); lblProcessor.Text = tmpProcess.TotalProcessorTime.ToString(); lblThreadCount.Text = tmpProcess.Threads.Count.ToString(); } catch (Exception ex) { //do nothing } } public static void Main() { Application.Run(new Exercise()); } } }
807
Stichwortverzeichnis Symbols
A
.asmx (Webdienst) 523 .NET –, .NET-Programmiersprache 18 –, Assemblies 52 –, Begriff 29 –, Kompilierung 30 .NET Framework 18 –, ASP.NET-Modul 423 –, Begriff 29 –, Dialogfelder 234 –, Erweiterbarkeit 590 –, installieren 33 –, Kapselung 590 –, Klassenbibliothek 36 –, Lokalisierung 669 –, SDK 33 –, Sicherheitsmerkmale 37 –, try-catch 683 –, Wiederverwendbarkeit 590 /bugreport 65 /debug 66 /main 66 /nologo 66 /optioncompare 66 /optionexplicit 66 /optionstrict 66 /out 66 /reference 66 /target 66 /win32icon 66 /win32resource 66 -Abschnitt 664 -Abschnitt 662 <startup>-Abschnitt 665 <system.windows.forms>-Abschnitt _Document 489 _WorkBook 495
Abfrage –, in Access 338 –, parametrisierte 338 Aborted 630 AbortRequested 630 abstrakte Klasse 302 Accept 428 AcceptButton-Eigenschaft 92 AcceptIt 91 AcceptsReturn-Eigenschaft 207 AcceptsTab-Eigenschaft 207 Access 19, 310, 314 –, Datenbank anlegen 314 –, Datentypen 316 –, gespeicherte Prozedur 338 –, Verbindungszeichenfolge 326 AccessibilityObject 677 AccessibleDefaultActionDescription 677 AccessibleDescription 677 AccessibleName 677 AccessibleRole 677 Account-Eigenschaft 548 Activated-Ereignis 100 Activate-Methode 93 Active Server Pages (ASP) 95 ActiveMdiChild-Eigenschaft 365 ActiveX 478, 516 –, ActiveX Control Importer 481 –, AxHost 480 –, AxHost-Klasse 591 –, AxSHDocVw.dll 482 –, Lizenzierung von Komponenten 501 –, Missing-Objekt 500 –, NetWord 722 –, OLE-Automatisierung 479 –, Richtlinien für die Verwendung 500 –, SHDocVw.dll 482 –, Sicherheit im Einsatz 502 –, sortieren 492
666
809
Stichwortverzeichnis
–, Steuerelement registrieren 499 –, Steuerelemente 480 –, Steuerelemente in .NET 499 –, Webbrowser 482 –, Wrapper 481 ActiveX Data Objects (ADO) 310 AddArc 458 AddBezier 458 AddBeziers 458 AddClosedCurve 458 AddCurve 458 AddEllipse 458 AddHandler-Methode 73, 99, 118, 161 –, Delegat 161 AddLine 458 AddLines 458 Add-Methode 71 –, Grafikobjekte 457 –, MainMenu-Objekt 116 AddPath 458 AddPie 458 AddPolygon 458 AddRectangle 459 AddRectangles 459 Address 428 AddString 459 AddTab-Methode 205 ADO 310 –, Typbibliotheken 346 ADO.NET 19, 37, 309 –, Begriff 310 –, Connection String 326 –, DataColumn-Objekt 294 –, DataMember-Eigenschaft 329 –, DataReader-Objekt 335 –, DataRelation 324 –, DataRow-Objekt 294 –, DataSet 312, 323 –, DataSource-Eigenschaft 329 –, DataTable-Objekt 294, 323 –, DataView-Objekt 341 –, Datenverwaltung 312 –, Datenzugriffsmodell 311 –, Integration von XML 311 –, Objektmodell 325 –, OLE DB-Provider 325
810
–, OleDbCommand-Objekt 335 –, OleDbDataReader-Objekt 335 –, Programmierbarkeit 312 –, Skalierbarkeit 312 –, SqlCommand-Objekt 325, 335 –, SqlConnection-Objekt 325 –, SqlDataAdapter-Objekt 325 –, SqlDataReader-Objekt 335 –, SQL-Provider 325 –, Steuerelemente 325 –, Transaktion 342 –, Verbindungszeichenfolge 326 –, XML 313 –, XmlDataDocument-Objekt 344 –, Zusammenspiel mit Datenbanken Adobe Framemaker 350 Adobe Photoshop 352 –, ini-Datei 657 Änderungsereignisse 106 Aktionen, Drag & Drop 105 Aktualisieren –, Form.Refresh 448 –, Formular 448 –, TreeView-Steuerelement 402 al.exe (Assembly Linker) 672 Aliasing 449 Alignment-Eigenschaft 454 –, StatusBar 135 AllowAutoRedirect 429 AllowDrop-Eigenschaft 92 AllowFullOpen 252 AllowWriteStreamBuffering 429 Alt-Eigenschaft 103 AlternatingBackcolor 292 Ampersand (kaufmännisches UndZeichen) 117 Anchor-Eigenschaft 222 Anforderung, senden 419 Anforderung/Antwort-Modell 416 Ansicht, benutzerdefinierte 341 ANSI-Zeichensatz 252 AntiAlias 450 AntiAliasGridFit 450 Antialiasing 256, 449 –, Dokumentvorschau 256 –, Schriftart-Dateien 450
311
Stichwortverzeichnis
–, TextRenderingHint-Eigenschaft 450 –, Wiedergabehinweise 450 Anwendung –, Aufruf im Browser 430 –, Bereitstellung 677 –, C# 46 –, CD-Sammlung 230 –, Datenbank 331 –, Datenquelle 290 –, Eingabehilfen 676 –, Einsprungspunkt 55 –, ereignisgesteuert 94 –, erstellen 39, 67 –, Fenster zeichnen 443 –, Formular posten 423 –, Funktionalität offen legen 478 –, hosted 478 –, kompilieren 40 –, kompilieren aus mehreren Dateien 242 –, konfigurieren 659 –, Leerlauf 96 –, Lokalisierung 669 –, MDI 350 –, Metadaten 31 –, multithreaded 622, 632 –, NetWord 261 –, Profilerstellung 696 –, Ressourcen 32 –, singlethreaded 628 –, Taschenrechner 68 –, Visual Basic .NET 46 –, webbasierte 311 –, Wiederverwendbarkeit 591 Appearance-Eigenschaft 183 –, TabControl-Steuerelement 206 Append 390 AppendText 386 application/x-www-form-urlencoded 425 Architektur –, CLR 32 –, Steuerelemente 592 –, Windows Forms-Steuerelemente 38 Archivdatei 678 Array 687 –, Datenquelle 284 ASC 321 ASP (Active Server Pages) 95
ASP.NET 416 –, Daten posten 425 –, Engine 659 –, Modul 423 –, Page_Load-Methode 423 –, Request-Objekt 423 –, Seite 423, 522 –, Tags 522 –, Webdienste 520 Assembly 49 –, Assembly Registration-Tool 499 –, Begriff 51 –, benutzerdefinierte Steuerelemente 609 –, Compiler 66 –, DLLs 53 –, Ressourcendatei 670 –, Steuerelemente einbetten 431 –, Übersicht 52 Assembly Linker 672 Attributes 386 Auf-Ab-Steuerelement 192 Aufzählung 268 –, Begriff 87 –, DialogResult 268 Ausführung –, kontrollieren 98 –, steuern 97 Ausnahme 682 –, abfangen 683 –, Hierarchie 684 –, throw 688 –, try-catch 683 Auswahlmodus 465 Authentifizierung 327 –, Basis 433 –, Digest 434 –, Integrierte Windows-Identifizierung 434 –, Internet 433 –, NTLM 434 AutoLog 543 AutoSize 200 AutoSize-Eigenschaft 135 AutoWert 315 Aximp.exe (ActiveX Control Importer) 481 AxSHDocVw.dll 482 AxWebBrowser-Objekt 484
811
Stichwortverzeichnis
B
BackColor 85, 292, 569 Background 630 BackGroundColor 292 BackgroundImage-Eigenschaft 85 base-Methode 60 BasePriority 700 Basisauthentifizierung 433 Basisklasse, Konstruktor 59 Befehlszeile –, Compiler 65 –, Compileroptionen 65 –, Dienst starten/beenden 552 BeginUpdate-Methode 185 Benutzer, anonymer 548 Benutzer-Agent (user-agent) 428 benutzerdefinierte Steuerelemente 590 –, Erzeugen aus vorhandenen 610 –, Kompilieren zu Assembly 609 –, OnPaint-Methode überschreiben 592 benutzerdefiniertes Ereignis, NetWord 707 Benutzereingaben handhaben 94 Benutzerkonto 548 Benutzername, ExtendedPropertiesObjekt 324 Benutzeroberfläche 68, 261 –, aufbauen 262 –, Datenbindung 283 –, Dienste 544 Benutzersteuerelemente 613 –, erstellen 615 –, Kapselung 615 –, UserControl-Klasse 615 Berechtigungen, Dienste 548 Bereich 442 –, Fensterform 471 Bereitstellung 656, 677 Besitzer, Steuerelemente 581 besitzergezeichnete Steuerelemente 568 Betriebssystem 622, 657, 698 –, Auslagerungsdatei 700 –, Betriebssystemkern 701 –, DOS 623 –, multithreaded 623 –, singlethreaded 623 –, Systemspeicher 700
812
–, Threadpooling 645 –, virtueller Speicher 701 Bezeichnungsfeld 178 –, Kontextmenü 125 Beziehung –, DataRelation-Objekt 324 –, Parent-Child-Menüs 364 –, Tabelle 323 Bézierkurve 457 BigInt 340 Bildlaufleiste 125, 136 –, AutoScoll-Eigenschaft 139 –, Bildlauf behandeln 141 –, Bildlauffeld 146 –, Ereignishandler 145 –, Ereignisse 143 –, HScrollBar-Steuerelement 136 –, LargeChange-Eigenschaft 143 –, Position 142 –, Scroll-Ereignis 141 –, ScrollEventHandler-Objekt 141 –, SmallChange-Eigenschaft 143 –, VScrollBar-Steuerelement 136 Bildlaufleiste 44 Bildschirm –, Koordinaten 603 –, PrimaryScreen-Eigenschaft 603 –, Screen-Klasse 603 Bildschirmanzeige –, Hide 92 –, Show 92 Binärdaten –, lesen 393 –, schreiben 393 –, Serialisierung 397 Binary 340 BinaryFormatter-Objekt 399 BindingContext-Objekt 286 Binding-Objekt 284 Bit 340 Bitmapgrafik 441 Bitmap-Klasse 450 Boolean 340 BorderStyle 132, 292 Bounds 569 BringToFront-Methode 93
Stichwortverzeichnis
Brinkster 537 Browser –, ActiveX-Steuerelement 482 –, AxWebBrowser-Objekt 484 –, Steuerelemente darstellen 431 –, Steuerelemente einbetten 430 Brush 442, 454 Brush-Objekt 460 BufferedStream 379 ButtonBase-Klasse 11 ButtonClick-Ereignis 130 Buttons-Auflistung 131 Button-Steuerelement –, Click-Ereignis 179 –, FlatStyle-Eigenschaft 179 –, PerformClick-Methode 179 Button-Steuerelement 11 Byte 340
C C 27 C# 27 –, Groß-/Kleinschreibung 47 –, Inherits 49 –, Typumwandlung 80 C++ 27 CAD-Anwendung, Profiling 702 Camel-Schreibweise 661 CancelButton-Eigenschaft 92 CancelIt 91 CanHandlePowerEvent 543 CanPauseAndContinue 543 CanShutdown 543 CanStop 543 CaptionBackColor 292 CaptionFont 292 CaptionForeColor 292 CaptionText 292 CaptionVisible 292 Casting 17 Casting-Operator 73, 163 CenterImage 200 Central Processing Unit (CPU) 622 ChangeColor-Methode 289 ChangeLocation-Methode 205 Char 340
CharacterCasing-Eigenschaft 207 CheckAlign-Eigenschaft –, CheckBox-Steuerelement 182 –, RadioButton-Steuerelement 182 CheckBoxes 220 CheckBox-Steuerelement 180 CheckBox-Steuerelement 11 CheckChanged-Ereignis 183 Checked 122, 571 CheckedListBox-Steuerelement 13, 18 CheckListBox-Steuerelement 221 CInt-Funktion 71 ClearTypeGridFit 450 Click-Ereignis 287 Client 417, 419 Client/Server-Modell 416 Clientbereich 593 ClientCertificates 429 Clipping-Region 605 CloneMenu-Methode 123, 275 Closed-Ereignis 97 Close-Methode 390 –, Formular 93 Closing-Ereignis 97, 100 CLR –, (Common Language Runtime) 31 –, animierte Mauszeiger 86 –, Architektur 32 –, Benutzersteuerelemente 615 –, Delegat 158 –, Ereignisse 158 –, Marshalling 500 –, Metadaten 32, 480 –, Ressourcen 32 –, verwalteter Code 32 Code –, Sperren 640 –, verwalteter 32 Color 454 ColorDialog 235, 250 ColorDialog 20 ColumnHeadersVisible 292 COM 479, 516 ComboBox 239 ComboBoxEdit 571 ComboBox-Steuerelement 17, 183
813
Stichwortverzeichnis
–, anpassen 570 –, Aussehen steuern 186 –, Datenbindung 289 –, Dropdown-Liste 187 –, DropDownStyle-Eigenschaft 186 –, einsetzen 187 –, Position 184 –, SelectedIndexChanged-Ereignis 187 –, SelectedIndex-Eigenschaft 187 –, sortieren 187 –, variable Elementhöhen 575 ComboBox-Steuerelement 13 Common Dialog Box (Standarddialogfeld) 235 Common Language Runtime (CLR) 32, 158 –, Begriff 31 CommonDialog 19 Compiler 41, 671, 721 –, Assemblies 66 –, Begriff 28 –, Metadaten 32 –, MSIL 32 –, Optionen 65 Compilerbefehl 65 Component Object Model (COM) 479 CompoundArray 454 Connection 429 ConnectionGroupName 427 Console.WriteLine-Methode 80 Constraint 72 ConstraintCollection 72 Containersteuerelement –, CheckBox-Steuerelement 182 –, Form 36 –, Panel 140 –, RadioButton-Steuerelement 182 ContentAlign-Aufzählung 179 ContentAlignment-Aufzählung 182 ContentLength 425 ContentType 427 ContextMenu 122 ContextMenu 25 ContinueDelegate 429 ContinuePending 558 ControlBox 89 Control-Eigenschaft 103 Control-Klasse 113, 178, 222, 592
814
Control-Klasse 2 Controls 17 Convert.ToInteger-Methode 73 Convert.ToString-Methode 73 CookieContainer 429 CopyTo 387 CorDbg.exe 694 CPU 622, 702 –, Mehrprozessorsystem 701 –, Prozessorzeit 701 CPU (Central Processing Unit) 622 Create 387, 390 CreateNew 390 CreateSubDirectory 388 CreateText 387 CreationTime 386 Credentials 427 CryptoStream 379 CStr-Funktion 71 CType-Methode 163 CultureInfo-Klasse 675 CurrentCellChanged-Ereignis 301 CurrentThread-Methode 629 Cursors-Objekt 86 CustomEndCap 454 CustomFormat-Eigenschaft 191 CustomStartCap 454
D DashCap 454 DashOffset 454 DashPattern 454 DashStyle 454 DataColumn 73 DataColumnCollection 73 DataGridBoolColumn 302 DataGridCell-Objekt 301 DataGridColumnStyle-Objekt 297 DataGrid-Steuerelement 221, 290 –, CurrentCellChanged-Ereignis 301 –, Daten bearbeitbar machen 330 –, Datenblatt 298 –, Farbe wechseln 302 –, Hit-Test 305 –, Interaktion 296 –, Stileigenschaften 296 –, Treffertest 305
Stichwortverzeichnis
–, UI-Eigenschaften 291 DataGrid-Steuerelement 28 DataGridTableStyle 297 DataGridTextBoxColumn 302 DataReader-Objekt 335 DataRelation 75 DataRelationCollection 75 DataRelation-Objekt 323 DataRow 294 DataRow 77 DataRowCollection 77 DataSet –, Begriff 323 –, Fill-Methode 328 DataSet 72, 79 DataSource 287 DataTable 81 DataTableCollection 81 DataTable-Objekt 294, 323 DataView 341 –, Sortierreihenfolge 342 DataView 85 DataViewRowState-Aufzählung 342 Datei –, anlegen 390 –, Begriff 378 –, Erweiterung 523 –, File.Exists-Methode 385 –, Inhalt löschen 390 –, kopieren 380 –, lesen 388 –, öffnen 390 –, Reader 388 –, Rekursion 382 –, schreiben 388 –, überwachen 402 –, Writer 388 Dateisystem 378, 380 –, Dateiüberwachungsdienst 553 –, überwachen 402 –, Überwachungsdienst 545 Dateityp 250 Daten –, Anwendungsdaten 283 –, bearbeiten 329 –, Begriff 282
–, Binärdaten 393 –, Datenanzeige 286 –, Datenquelle 283, 290 –, gebundene Daten bearbeiten 287 –, mit Formularen verbinden 290 –, Quelle 283 –, senden an ASP.NET-Seite 424 –, verbinden mit Schaltfläche 285 –, XML-Datei 284 –, XML-Daten 284, 310 Datenbank 283 –, Access 314 –, ADO 310 –, anlegen 314 –, Anwendung 331 –, Authentifizierung 327 –, AutoWert 315 –, DataTable-Objekt 323 –, Datenbankzugriff 327 –, Ergebnisse anzeigen 336 –, Identitätsspalte 315 –, Primärschlüssel 316 –, Profiling 702 –, SQL 310 –, SQL Server 317 –, Systemadministrator 327 –, Tabelle 284 Datenbankverbindung 325 –, Verbindungszeichenfolge 326 Datenbankzugriff 37 Datenbindung 282 –, Begriff 282 –, BindingContext-Eigenschaft 290 –, ComboBox-Steuerelement 289 –, DataSource-Eigenschaft 287 –, Datenquelle 284 –, einfache 283 –, Feld 284 –, Formular 290 –, IList-Schnittstelle 284 –, Kombinationsfeld 287 –, komplexe 283, 288 –, Schaltfläche 285 –, TextBox-Steuerelement 333 Datenblatt 298 –, Wert der Zelle 301
815
Stichwortverzeichnis
Datenblattansicht 314 Datenmenge (DataSet) 323 Datenmodell 282 Datenschicht 283 Datentyp –, .NET 339 –, Access 316 –, AutoWert 315 –, Casting-Operator 73 –, SQL Server 318 –, SqlDbType 339 –, Umwandlung 73 DateTime 192, 340 –, formatieren 303 –, Zeit, aktuelle 217 DateTimePicker-Steuerelement 189 –, Ereignisse 193 –, Formatzeichenfolge 191 DateTimePicker-Steuerelement 32 Datum und Uhrzeit, Formatzeichenfolge 303 Datum, Kontrollkästchen 192 DbgClr.exe (Common Language Runtime Debugger) 690 Deactivate-Ereignis 100 Debugger 686 –, Befehlsfenster 693 –, Lokalfenster 693 –, Schaltflächen für das Bewegen im Code 694 –, Überwachen-Fenster 694 Debugging 682 –, Ablaufschritte 690 –, Ausnahme-Framework 684 –, CLR-Debugger (DbgClr) 690 –, CorDbg.exe 694 –, Debugger anhängen 689 –, Fehlerbehandlung 666, 685 –, Haltepunkt 692 –, Informationen über 659 –, JIT-Debugger 666 Befehlszeilenversion 694 –, JIT-Debugging 689 –, Programm-Datenbankdatei 690 –, Sensibler Code 683 –, try-catch 683, 689 –, Windows-Dienst 563
816
Decimal 340 Default 571 DefaultItem 122 Delegat 158, 265 –, AddHandler-Methode 161 –, Begriff 159 –, benutzerdefinierter 169, 605 –, besitzergezeichnetes Steuerelement 569 –, erstellen 162 –, hinzufügen 276 –, implementieren 161 –, Parameter 162 –, SelectedIndexChanged-Ereignis 188 –, Trennen von Ereignis 161 –, Typ 164 delegate 169 DELETE 323 Delete 387 Deployment 17 DESC 321 Deserialisierung 397 –, Begriff 397 –, Deserialize-Methode 399 Designer, Windows Forms 64 DesktopLocation-Eigenschaft 84, 205 Destruktor 58 –, Begriff 58 –, nothing 58 –, null 59 Dialogfeld 232 –, Aufgaben 232 –, Benutzereingabe erzwingen 234 –, ColorDialog 235, 250, 365 –, Datei öffnen 239, 248 –, Drucken 255 –, eigenes erstellen 238 –, Ergebnis 244 –, Ersetzen 272 –, Farbauswahl 251 –, FileOpenDialog 268 –, FileSaveDialog 268 –, FindDialog 269 –, FontDialog 235, 252, 269 –, FormBorderStyle-Eigenschaft 233 –, Größenziehpunkt 243 –, Informationen abrufen 244
Stichwortverzeichnis
–, Klasse erstellen 238 –, Meldungsfeld 232 –, MessageBox 235 –, modales 234, 242 –, Modalität 233 –, NetWord 268 –, nichtmodales 234, 243, 260, 361 –, OpenFileDialog 235, 509 –, PageSettings 253 –, PageSetupDialog 235, 253, 268 –, PrintDialog 235, 255, 268 –, PrintDocument 256 –, PrintPreviewDialog 235, 248, 256, 268 –, SaveFileDialog 235 –, Schriftauswahl 252 –, Seite einrichten 255 –, Seitenvorschau 257 –, Speichern 248 –, Standard 235, 248 –, Steuerelemente 239 –, Suchen 272 –, technische Definition 233 Dialogfeldklasse, benutzerdefinierte 243 DialogResult 244 Dienstbeschreibung –, untersuchen 528 –, wsdl.exe 528 Dienste –, Begriff 29 –, Benutzeroberfläche 544 –, Berechtigungen 548 –, Dateisystem überwachen 545 –, deinstallieren 552 –, Druckspooler 540 –, Ereignisprotokoll 546, 560 –, erzeugen 542 –, ExecuteCommand-Methode 559 –, Installer 547 –, Konto 548 –, Meldungsfeld 544 –, OnCustomCommand-Methode 559 –, Schnittstellen 555 –, Service Control Manager (SCM) 542 –, ServiceBase-Klasse 542 –, ServiceControllerStatus-Aufzählung 558 –, starten/beenden 552
–, Status 558 –, überwachen 553 –, Verwaltung 542 –, Webserver 540 –, Windows 540 Digestauthentifizierung 434 Directory 386 DirectoryInfo-Objekt 380 –, Mitglieder 387 DirectoryName 386 Directory-Objekt 380 DirectX 441 Disabled 571 disco.exe 527 DisplayInfo-Methode 384 Divider-Eigenschaft 132 DLL –, Assemblies 53 –, Begriff 53 Dock-Eigenschaft 210, 222 –, RichTextBox-Steuerelement 210 Document-Klasse 353 Dokument –, Antialiasing 256 –, drucken 406 –, MDI 350 –, NetWord 509 –, Objekttypen 361 –, PrintDocument-Steuerelement 50 –, Randeinstellung 253 –, Seiteneigenschaften 254 –, speichern 510 –, untergeordnetes 353, 509 –, verknüpftes Menü 357 –, Vorschau 256 Domain Naming System (DNS) 417 DomainUpDown-Steuerelement 35 DOS, Singlethreaded-Betriebssystem 623 Double 340 Drag & Drop 105 DragDropEffects-Aufzählung 105 DragDrop-Eigenschaft 105 DragEnter-Eigenschaft 105 DragLeave-Eigenschaft 105 DragOver-Eigenschaft 105 DrawBackground 570
817
Stichwortverzeichnis
DrawFocusRectangle 570 DrawImage-Methode 451 DrawItemEventArgs 569 DrawItemState-Aufzählung 570 DrawMode 568 DrawString-Methode 409, 444 Dreischichtenmodell 283 DropDownButton 133 Dropdown-Liste –, DateTimePicker-Steuerelement 189 –, Kombinationsfeld 187 Drucken 253, 378, 406 –, Assembly 255 –, Druckschleife 512 –, Druckvorschau 718 –, HasMorePages-Eigenschaft 410 –, Namensraum 255 –, NetWord 511 –, objPageSettings-Objekt 511, 708 –, PrintDocument-Objekt 406 –, Print-Methode 511 –, PrintPage-Ereignis 406, 511 –, Seiteneigenschaften 254 –, Standarddialogfeld 253
E E/A 17 EditColor-Methode 289 Editor 18, 39 –, Quellcode 34 –, Visual Studio .NET 34 Eigenschaft –, AcceptButton 92 –, Ändern per JavaScript 432 –, AllowDrop 92 –, BackColor 85 –, BackgroundImage 85 –, BorderStyle 132 –, CancelButton 92 –, DesktopLocation 84 –, Divider 132 –, Drag & Drop 105 –, ForeColor 84 –, FormBorderStyle 86 –, Formulare 82 –, Height 83
818
–, Icon 85 –, ImageIndex 128 –, IsMdiContainer 354 –, Item 119 –, KeyChar 101 –, Left 84 –, Location 84 –, MdiParent 354 –, NotifyFilter 402 –, Opacity 88 –, Shortcut 121 –, ShownInTaskBar 86 –, Size 83 –, SqlDataAdapter-Objekt 329 –, Tastatur 103 –, Textfarbe 84 –, ToolBar-Steuerelement 131 –, Top 84 –, TransparencyKey 88 –, Visible 88 –, Width 83 –, zurücksetzen 93 Eigenschaftendeklaration 595, 605 Ein-/Ausgabe 248, 378 –, asynchrone 405 –, NetWord 507 –, synchrone 405 Eingabe –, maskieren 207 –, PasswordChar 207 Eingabeaufforderung 40 Eingabehilfen 656, 676 –, Microsoft Active Accessibility (MSAA) Einsprungspunkt 55 Einzug 207 Empfänger-Objekt 159 Enabled 122 EndCap 454 EndScroll 143 EndUpdate-Methode 186 Enter 640 Entwicklungsumgebung 34 Entwurfsansicht 314 Equals-Methode 80 Ereignis 94 –, Activated 100
676
Stichwortverzeichnis
–, Begriff 94 –, behandeln 153 –, benutzerdefiniertes 163 –, Bildlaufleiste 143 –, ButtonClick 130 –, Closed 97 –, Closing 97 –, CurrentCellChanged 301 –, Deactivate 100 –, Delegat 158 –, Empfänger 158 –, Ereignissystem 173 –, Handler 101 –, instantiieren 165 –, KeyPress 101 –, mehrere behandeln 156 –, nothing 165 –, null 165 –, Paint 443 –, Popup 369 –, Resize 139 –, Sender 158 –, Verdrahtung 152 –, WithEvents 226 Ereignisanzeige 555 Ereignisbehandlung 94 –, mehrfache Ereignisse 156 Ereignishandler 147, 152, 686 –, Begriff 101, 152 –, besitzergezeichnetes Steuerelement 569 –, Bildlaufleiste 145 –, Click-Ereignis 287 –, Dateisystem überwachen 404 –, mehrere Ereignisse behandeln 156 –, NetWord 266 –, PrintPage 407 –, Signatur 155 –, Standardsignatur 155 –, Thread 631 Ereignisprotokoll –, anpassen 560 –, Begriff 553 –, Dienste 546 –, Ereignisse löschen 555 –, FileSystemWatcher 547 –, ServiceController-Klasse 560 –, Systemereignisprotokoll 546
ErrorProvider-Steuerelement 42 Ersetzen, Dialogfeld 272 Erweiterbarkeit 590 event 164 EventArgs-Parameter 162 EventHandler-Objekt 160 EventLog 543 EventLog-Klasse 560 Excel 234, 350 –, Bereich holen 496 –, get_Range-Methode 496 –, sortieren 492 –, Typbibliothek 491 –, via ActiveX verwenden 491 excel9.olb 491 Exception 17 Exception-Klasse 682 ExecuteCommand-Methode 559 ExecuteNonQuery-Methode 335 ExecuteReader-Methode 336 ExecuteScalar-Methode 335 ExecuteXmlReader-Methode 335 Exists 386 Exit 640 ExitCode 700 ExitTime 700 Expect 429 ExtendedProperties-Objekt 324 eXtensible Markup Language (XML) 517 Extension 386
F Farbe –, Begriff 601 –, DataGrid-Steuerelement 302 –, Pinsel 601 –, RGB 601 –, Standarddialogfeld 250 Farbverlauf 461 Fehlerbeseitigung 17 Fenster –, aktives MDI-Fenster 365 –, übergeordnetes 353 –, zeichnen 443 Fenster-Menü 352 Festplatte überwachen 403
819
Stichwortverzeichnis
Figur –, Bézierkurve 457 –, füllen 442, 460 –, Pfad 456 –, zeichnen 454 File Transfer Protocol (FTP) 17 File.Copy-Methode 380 File.Exists-Methode 385 FileDialog-Klasse 20 FileInfo-Objekt 380, 687 FileMode-Aufzählung 390 FileName-Eigenschaft 248 File-Objekt 380 FileStream 379 FileSystemWatcher-Objekt 402 FillClosedCurve 462 FillEllipse 462 Fill-Methode 328 FillPath 462 FillPie 462 FillPolygon 462 FillRectangle 462 FillRectangles 462 FillRegion 462 Filter 249, 341 –, Benutzeraufforderung 250 –, Beschreibung 250 –, Filterzeichenfolge 250, 259 –, NotifyFilter-Eigenschaft 402 –, Standardfilter 250 Firewall 312 First 143 Fixed3D 87 FixedDialog 87 FixedSingle 87 FixedToolWindow 87 Fläche 442 FlatMode 292 Float 340 Focus 571 Fokus –, RichTextBox-Steuerelement 207, 266 –, TextBox-Steuerelement 207 –, untergeordnetes MDI-Fenster 367 Font 84, 444, 569 FontDialog-Dialogfeld 235, 252
820
FontDialog-Dialogfeld 22 Fontfamilie 252 FontFamily-Objekt 85 foreach-Schleife 382 ForeColor 292, 569 ForeColor-Eigenschaft 84 Form.Refresh 448 Formatierung, RichTextBoxSteuerelement 208 Formatzeichenfolge –, CustomFormat-Eigenschaft 191 –, DataGrid-Steuerelement 303 –, DateTimePicker-Steuerelement 191 –, Datum und Uhrzeit 303 –, Groß-/Kleinschreibung 191, 304 –, Kalender 191 FormBorderStyle-Aufzählung 87 FormBorderStyle-Eigenschaft 86, 233 FormClicked-Ereignis 448 Form-Ereignisse 97 –, Maus und Tastatur 101 Form-Objekt 84, 113 –, Menu-Eigenschaft 116 –, Methoden 92 –, Resize-Ereignis 139 Form-Steuerelement 36 Formular –, Activate-Methode 93 –, ActiveMdiChild-Eigenschaft 365 –, aktualisieren 93, 448 –, Bildlaufleiste 136 –, Close-Methode 93 –, Datenbindung 290 –, digitales 35 –, Eigenschaften 82 –, Größe 83 –, Handle 593 –, Hide-Methode 92, 240 –, in Vordergrund/Hintergrund 93 –, Position 83 –, posten 422 –, Show-Methode 92 –, sichtbar/unsichtbar machen 92 –, übergeordnetes 354 –, Visible-Eigenschaft 88 Formular-Methoden 92
Stichwortverzeichnis
Friend 48 FromFile-Methode 85 FromImage-Methode 469 FrontPage 18 FTP 416 Füllmodus 465 FullName 382, 386 FullRowSelect 220 Function 54 Funktionen –, CInt 71 –, CStr 71 –, MsgBox 99
G Ganzzahlen umwandeln 71 Garbage Collection 32 GDI 440 GDI+ 441 –, Begriff 37 –, Bereiche 441, 471 –, Brush-Objekte 460 –, DrawImage-Methode 451 –, DrawString-Methode 444 –, Figuren ausfüllen 460 –, Figuren beschneiden 471 –, Figuren dauerhaft machen 468 –, Figuren zeichnen 454 –, Fill-Methoden 463 –, Graphics-Objekt 442, 569 –, GraphicsPath 456, 600 –, Linien zeichnen 455 –, Matrix-Objekt 470 –, Metafile-Objekt 474 –, Paint-Ereignis 443 –, Pfade 456 –, Regions 471 –, Steuerelemente anpassen 567 –, Transformationen 470 –, Transparenz 474 gespeicherte Prozedur 338 get_Range-Methode 496 Get-Anforderung 422 GetConfig-Methode 82 GetData-Methode 426 GetDirectories 384, 388
GetFiles 382, 388 GetFileSystemInfos 388 GetHashCode-Methode 81 Get-Methode, SqlDataReader-Objekt 337 Get-Request 422 GetRequestStream 427 GetResponse 427 GetResponseStream 428 GetType-Methode 82, 178 GetWorkingArea-Methode 83 GetXml-Methode 342 globaler Namensraum 51 Grafik –, Aliasing 449 –, Antialiasing 449 –, anzeigen 450 –, bearbeiten 452 –, Bézierkurve 457 –, Bitmap 441 –, Bogen 457 –, DirectX 441 –, Ellipse 457 –, Figur transformieren 470 –, GDI 440 –, Kreisform 457 –, Kurve 457 –, OpenGL 441 –, PathPointType-Aufzählung 460 –, Polygon 457 –, Transformation 453 –, transparent 193 –, transparent machen 474 –, Typografie 441 –, Vektorgrafik 441 –, Verarbeitung 440 –, verzerren 451 –, Wiedergabehinweise 450 Graphical Device Interface (GDI+) 440 Graphics 442, 444, 569 –, FromImage-Methode 469 –, Transformation 470 Grauabstufung 17 Grayed 571 GridLineColor 292 GridLineStyle 292 Größenziehpunkt, Dialogfeld 243
821
Stichwortverzeichnis
Groß-/Kleinschreibung –, C# 47 –, Camel-Schreibweise 661 –, CharacterCasing-Eigenschaft 207 –, Eingabe in Textfeld 207 –, Formatzeichenfolge 191, 304 GroupBox-Steuerelement 226 GroupBox-Steuerelement 43 Grundfarben 601 Gruppenfeld 226 Gültigkeitsprüfung 182 Guid 340
HScrollBar-Steuerelement 45 HTML 421 –, ActiveX-Steuerelement Webbrowser HTML-Steuerelemente 38 HTTP 416, 517 –, SOAP 520 HTTP-Get 520, 535 HTTP-Header 429 HTTP-Post 520, 535 HTTPS 433 HttpWebRequest 428
H
Icon-Eigenschaft 85 –, StatusBarPanel 135 Id 700 IDE 63 Identitätsspalte 315 idling 96 IfModifiedSince 429 IIS (Internet Information Server) 419, 540 ildasm.exe (Intermediate Language Disassembler) 484, 679 IList-Schnittstelle 284 Image 85, 340 ImageIndex 128f. ImageIndex-Eigenschaft 194 Image-Klasse 450 ImageList 220 –, Symbolleiste 128 ImageList-Steuerelement 193 –, ImageIndex-Eigenschaft 194 –, Images-Eigenschaft 193 ImageList-Steuerelement 45 Importieren, Namensräume 51 Imports 40, 49 Inactive 571 Indent 220 Index 570 IndexOf-Eigenschaft 131 Info-Menüelement 660 Inherits 49 InnerException 688 InputLanguageChanged-Ereignis 100 INSERT 322 –, VALUES 322
Handle 700 –, Begriff 593 –, Formular 593 HandleCount 700 Handled-Eigenschaft 103 Handles (Schlüsselwort) 161 HasExited 700 Hash-Code 81 HasMorePages-Eigenschaft 410 HatchBrush-Objekt 461 Hauptprozessor 17 HaveResponse 429 HeaderBackColor 292 HeaderFont 292 HeaderForeColor 292 Headers 427 HeaderText-Eigenschaft 298 HelpButton 89 HelpLink 688 Hide-Methode 240 Hintergrundfarbe 250 –, Datenbindung 290 –, MDI-Formular 357 –, Steuerelement 569 –, TabControl-Steuerelement 581 Hintergrundthread 629 Hinweis, Antialiasing 450 Hit-Test 305 HitTest-Methode 305 HotLight 571 HotTrack-Eigenschaft 206 HotTracking 220
822
482
I
Stichwortverzeichnis
Installer –, Dienste 547 –, erstellen 547 –, Hilfsprogramm 550 –, Klasse 547, 549 –, Werkzeug 549 –, Windows-Dienst 542 Installer (Klasse) 542 installutil.exe 549 Instanz –, Begriff 54 –, Speicheradresse 81 Int 340 Int16 340 Int32 340 Int64 340 integrierte Entwicklungsumgebung (IDE) 63 Interaktivität 88 Intermediate Language Disassembler 484, 504, 679 Internet 416, 516, 625 –, Kommunikationsprotokolle 517 –, Netzwerkbandbreite 625 –, Pluggable Protocols 418 –, Protokolle 416, 516 –, Protokolle, austauschbare 418 Internet Explorer 120, 416 –, Arbeiten mit 430 –, Steuerelemente einbetten 430 Internet Information Server (IIS) 419 Internetsicherheit 433 –, Authentifizierung 433 –, Basisauthentifizierung 433 –, Berechtigungsklassen 434 –, Digestauthentifizierung 434 –, HTTPS 433 –, Integrierte Windows-Authentifizierung 434 –, NetworkCredential-Objekt 434 –, Secure Sockets Layer 433 Invalidate-Methode 597 IP 416 IsAccessible 677 IsMdiContainer-Eigenschaft 354 Isolierte Speicherung 412 Item-Eigenschaft 119 Items-Auflistung 184 IWin32Window 236
J Java 27 JavaScript, Eigenschaften ändern JET 17 JIT-Compiler 31, 666 JIT-Debugger 666, 686 Join 323 Just-In-Time 31
432
K Kalender 189 –, DateTimePicker-Steuerelement 189 –, Textbereich ausrichten 192 Kapselung 591 –, Begriff 48 –, Benutzersteuerelemente 615 kaufmännisches Und-Zeichen 117 KeepAlive 429 Kennwort –, Eingabe maskieren 207 –, ExtendedProperties-Objekt 324 –, PasswordChar 207 KeyChar-Eigenschaft 101 KeyCode-Eigenschaft 103 KeyData-Eigenschaft 103 KeyEventHandler 101 KeyPress-Ereignis 101 KeyPressEventHandler 101 KeyValue-Eigenschaft 103 Klasse 47 –, abstrakte 302 –, Begriff 47 –, Instanz 54 –, nothing 58 –, null 59 Klausel –, ORDER BY 321 –, VALUES 322 –, WHERE 320 Knoten 218 Kombinationsfeld 17, 183 –, Datenbindung 287 –, Dropdown-Liste 187 –, Höhe 187 –, Items-Auflistung 184 –, variable Elementhöhen 575
823
Stichwortverzeichnis
Kompatibilität 31 Kompilieren 63 –, Anwendung 40 –, Listings in mehreren Dateien 242 Konfiguration –, Abschnitte in Datei 660 –, AppSettings-Eigenschaft 668 –, benutzerdefinierte Datei 659 –, ConfigurationSettings-Klasse 667 –, Datei des .NET Frameworks 659 –, Datei in .NET 658 –, Datei in XML 658 –, Datei machine.config 659 –, Daten liefern 657 –, GetConfig-Methode 667 –, Konfigurationsabschnitte für Windows Forms 661 –, Konfigurationselement 660 –, Probleme 656 –, Version 665 –, Werte abrufen 667 Konstruktor 40, 263, 633 –, Basisklasse 59 –, Begriff 58 –, erben 59 –, Standardkonstruktor 210 –, überladen 60 Kontextmenü 122 –, Begriff 122 –, erstellen 122 –, kopieren 123 –, NetWord 274 Konto –, Account-Eigenschaft 548 –, Berechtigungen 548 –, Dienste 548 –, ServiceAccount-Aufzählung 548 Kontrollhäkchen 183 Kontrollkästchen –, DataGridBoolColumn 297 –, Datum 192 Konventionen 21 Koordinaten 276, 709 –, Bildschirm 603 –, Maus 603 –, übersetzen 603, 605
824
Kopieren –, Datei 380 –, File.Copy-Methode Kreisform 457
380
L LabelEdit 220 Label-Steuerelement 202 –, persistent 449 –, Tabseite 203 Label-Steuerelement 46 Ländereinstellungen 669 –, Kultur 672 LargeChange-Eigenschaft 143 LargeDecrement 143 LargeIncrement 143 Last 143 LastAccessTime 386 LastWriteTime 386 Layout –, MDI-Fenster 358 –, MdiLayout-Aufzählung 358 –, Steuerelemente 222 Leerlauf 96 –, Nachrichtenschleife 96 Left-Eigenschaft 84 Legacy-Programme 29 Length 386 LIKE 321 LineJoin 454 Linie –, PathPointType-Aufzählung 460 –, zeichnen 442 LinkColor 293 LinkHoverColor 293 LinkLabel-Steuerelement 46 ListBox-Steuerelement 17, 195 –, anpassen 570 –, benutzerdefiniertes 574 –, Elemente auswählen 195 –, MultiColumn-Eigenschaft 196 –, SelectedIndices-Eigenschaft 198 –, UseTabStops-Eigenschaft 198 ListBox-Steuerelement 13 ListControl-Klasse 13 Liste –, Dropdown-Liste 190, 239
Stichwortverzeichnis
–, hierarchische 230 –, Index 189 –, nullbasiert 188 Listenfeld 17, 195 –, anpassen 571 –, benutzerdefiniertes 574 –, FindStringExact-Methode 199 –, Text suchen 199 ListView-Steuerelement 47 LoadFile-Methode 401, 686 localhost 327 LocalService-Konto 548 LocalSystem-Konto 548 Location-Eigenschaft 84 Lock 641 Lokalisierung 656, 669 –, Ablauf 670 –, Begriff 669 –, CultureInfo-Klasse 675 –, Dateinamen 672 –, ResourceManager-Klasse 674 –, Ressourcen 669 übersetzen 676
M machine.config 659 MachineName 700 MainMenu 116, 154 MainMenu 26 Main-Methode 54, 69, 240, 242, 263 –, untergeordnetes Dokument 353 MainModule 700 MainWindowHandle 700 MainWindowTitle 700 Managed Code 32 MappingName 17 Marshalling 500, 634 –, threadübergreifendes 637 Maschinencode, JIT-Compiler 31 Maschinensprache 27 Matrix-Objekt 470 –, Methoden 471 Maus –, Ereignisse 101, 104 –, Koordinaten 603
Mauszeiger –, animierte 86 –, Arrow 86 –, Bildschirmkoordinaten 104 –, Cursor-Eigenschaft 86 –, Ereignisse 94 –, IBeam 86 –, QuickInfo 221 –, ResetCursor-Methode 448 –, zurücksetzen 448 MaximizeBox 89 MaximumAutomaticRedirections 429 MaxWorkingSet 700 MDI 262, 350 –, Adobe Framemaker 351 –, aktives Fenster 365 –, Anwendung 350, 705 –, Anwendung erstellen 349, 353 –, Child-Dokumente 353 –, Excel 350 –, Fensteranordnung 358 –, Fenster-Menü 352 –, hinzufügen 505 –, Menu Merging 362 –, Menü 352 –, Menüleiste 357 –, Menüs zusammenführen 362 –, MergeOrder-Eigenschaft 364 –, Nachteile 352 –, Objekttypen untergeordneter Dokumente 361 –, Parent-Fenster 353 –, übergeordnetes Fenster 353 –, übergeordnetes Formular 355 –, übergeordnetes Steuerelement 368 –, untergeordnete Fenster 365 –, untergeordnete Steuerelemente 367 –, untergeordnetes Dokument 353 MdiLayout-Aufzählung 358 MdiList-Eigenschaft 360 MdiParent-Eigenschaft 354 Me 58 MediaType 429 Mehrfachauswahl 199 Meldungsfeld 188 –, DialogResult 245
825
Stichwortverzeichnis
MemoryStream 379 Menu Merging 362 Menü 146 –, anpassen 120, 581 –, Begriff 114 –, benutzerdefiniertes 581 –, Besitz 581 –, dynamisches 587, 590 –, Größe der Zeichenfolge messen 585 –, Handler 118 –, hinzufügen 114 –, Kontextmenü 122 –, kopieren 123 –, MDI 352 –, Menu Merging 362 –, Menüebenen 119 –, NetWord 274 –, OwnerDraw-Eigenschaft 581 –, Popup-Ereignis 369 –, Separator 115 –, Shortcut 121 –, Tastenkombination 117 –, Trennlinie 115 –, übergeordnetes Formular 357 –, Überblick 25, 33 –, Übersicht 25 –, verschachteln 119 –, Zugriffstaste 117 –, zusammenführen 362 Menüelement –, Eigenschaften 122 –, Häkchen 157 –, Info 660 –, MergeOrder-Eigenschaft 364 MenuItem 116 –, OwnerDraw-Eigenschaft 581 MenuItem 26 MenuMerge-Aufzählung 363 MergeOrder-Eigenschaft 364 Message 688 MessageBox (Meldungsfeld) 188 MessageBox.Show-Methode 188 MessageBoxOptions-Eigenschaft 238 Metadaten 37, 397, 480 –, Begriff 31 –, CLR 32
826
Metafile-Objekt 474 Method 427 Methode –, AcceptIt 91 –, Add 71 –, AddHandler 73 –, Begriff 54 –, BringToFront 93 –, CancelIt 91 –, Console.WriteLine 80 –, Equals 80 –, Form-Objekt 92 –, FromFile 85 –, GetConfig 82 –, GetHashCode 81 –, GetType 82 –, GetWorkingArea 83 –, Main 54 –, Missing 489 –, MsgBox 99 –, New 58 –, Parameter, optionale 489 –, ReferenceEquals 81 –, rekursive 382 –, Run 56 –, SendToBack 93 –, Shared 55 –, static 55 –, ToString 82 –, Typen 54 MFC (Microsoft Foundation Classes) 27 Microsoft .NET 17 Microsoft Foundation Classes 28 Microsoft Intermediate Language (MSIL) 31 Microsoft Money 308 Microsoft Office 2000 –, ActiveX 486 –, Dialogfeld, nichtmodales 234 –, dynamische Menüs 568, 587, 590 –, Objektkatalog 497 –, Typbibliothek 485 Microsoft Paint 440 MinimizeBox 89 MinWorkingSet 700 Missing 489 MiterLimit 454
Stichwortverzeichnis
modales Dialogfeld 234 Modifiers-Eigenschaft 103 Modules 700 Money 340 Monitor-Klasse 639 MouseDown-Eigenschaft 104 MouseEnter-Eigenschaft 104 MouseEventhandler-Objekt 104 MouseHover-Eigenschaft 104 MouseLeave-Eigenschaft 104 MouseMove-Eigenschaft 104 MouseUp-Eigenschaft 104 MoveTo 387 MSAA 676 MsgBox-Methode 99 MSIL (Microsoft Intermediate Language) 31, 40, 480 –, DLL 53 msword9.olb 486 Multiline-Eigenschaft 206 Multiple Document Interface (MDI) 350 Multitasking-Betriebssystem 30, 350 Multithreading 19, 622 –, Anwendung 622, 625 –, Anwendung erstellen 628 –, Betriebssystem 623 –, Browser 625 –, Steuerelemente 652 –, Suchanwendung 652 –, Suchfunktion 644
N Nachrichtenschleife 94 Name 386 Namensraum 49 –, ActiveX 482 –, Assemblies 51 –, globaler 51 –, importieren 51 –, Objektgruppen 40 –, Standard 51 –, Steuerelemente einbetten –, System 50 –, Webdienst 523 –, Word 489 NChar 340
431
NetWord 261 –, ActiveX 722 –, benutzerdefinierte Bildlaufleisten 717 –, benutzerdefiniertes DialogfeldSteuerelement 705 –, benutzerdefiniertes Ereignis 707 –, Dialogfelder 268 –, Document-Klasse (revidiert) 709 –, Drucken 511 –, Ein-/Ausgabe 508 –, Ereignishandler 266 –, erweitern 505 –, FindDialog-Klasse (revidiert) 718 –, Kontextmenü 274 –, MDI-Anwendung 505 –, Menü 274 –, OpenFileDialog-Dialogfeld 509 –, Page-Klasse 705 –, Schriftart 269 –, Schriftfarbe 269 –, Statusleiste 275 –, Suchen und Ersetzen 272 –, Tastenkombination 274 –, vervollständigen 705 NetworkCredential-Objekt 434 NetworkService-Konto 548 NetworkStream 379 Netzwerkzugang 378 New-Methode 58 nichtmodales Dialogfeld 234 Nichtverwalteter Code 32 NoAccelerator 571 Nodes 220 NoFocusRect 571 None 87, 571 NonInheritable 107 Non-Managed Code 32 NonpagedSystemMemorySize 700 Normal, PictureBox-Steuerelement 200 NotePad 18, 34, 693 nothing 58, 165 NotifyFilter-Eigenschaft 402 NText 340 null –, Destruktor 59 –, Ereignis 165
827
Stichwortverzeichnis
NumericUpDown-Steuerelement NVarChar 340
39
O Object Linking and Embedding (OLE) 478 Object-Klasse 178 Object-Objekt 79 OBJECT-Tag 430 Objekt –, Begriff 38 –, Binding 284 –, BindingContext 286 –, Eigenschaften 38 –, Hash-Code 81 –, Methoden 38 –, Object 79 –, Screen 83 –, Serialisierung 396 –, Size 83 –, Speicheradresse 81 –, Sperren 640 –, zuweisen an Variable 70 Objektinstanz 54 Objektorientierte Programmierung (OOP) 38 OLE (Object Linking and Embedding) 478 OLE DB 19 –, Datenquelle 311 OLE DB-Provider 325 OLE-Automatisierung 479 OleDbCommand 87 OleDBCommandBuilder 88 OleDbCommand-Objekt 335 OleDbConnection 88 OleDbDataAdapter 90 OleDbDataReader 86, 92 OleDbDataReader-Objekt 335 OleDbError 93 OleDbErrorCollection 93 OleDbParameter 94 OleDbParameterCollection 94 OleDbTransaction 96 OnContinue 544 OnCustomCommand 544 OnCustomCommand-Methode 559 On-Methode 165 OnPaint-Methode 444
828
OnPause 544 OnPowerEvent 544 OnShutdown 544 OnStart 544 OnStop 544 OOP (Objektorientierte Programmierung) Opacity-Eigenschaft 88 Open 387, 390 OpenFileDialog-Dialogfeld 235 OpenFileDialog-Dialogfeld 20 OpenFile-Methode 687 OpenGL 441 OpenOrCreate 390 OpenRead 387 OpenText 387 Option Strict 79 Optionen, Compiler 65 Oracle 284, 310 ORDER BY 321 –, ASC 321 –, DESC 321 Ordner überwachen 402 Overloads 62 Override 445
P Padding-Eigenschaft 206 Page_Load-Ereignis 423 Page_Load-Methode 423 PagedMemorySize 700 PagedSystemMemorySize 700 PageSetupDialog-Dialogfeld 235 PageSetupDialog-Dialogfeld 23 Paint-Ereignis 443 PaintEventArgs-Objekt 444 Panel-Steuerelement 140 Panel-Steuerelement 39 Parameter –, Delegat 162 –, Missing 489 –, optionale 489 –, Show-Methode 236 PARAM-Tag 431 Parent 387 ParentRowsBackColor 293 ParentRowsForeColor 293
38
Stichwortverzeichnis
ParentRowsLabelStyle 293 ParentRowsVisible 293 PathPointType-Aufzählung 460 PathSeparator 220 Paused 558 PausePending 558 PeakPagedMemorySize 700 PeakVirtualMemorySize 701 PeakWorkingSet 701 Peek-Methode 392 Pen 442 –, Aufzählungen 455 –, Eigenschaften 454 PenType 454 Pfad –, definieren 456 –, File.Exists-Methode 385 Pfadbezeichner 418 Photoshop 234 PictureBox-Steuerelement 199 –, Image-Eigenschaft 199 –, SizeMode-Eigenschaft 199 PictureBox-Steuerelement 50 Pinsel 442 –, Farbe 601 –, Farbverlauf 461 –, Typen 461 Pipelined 429 Pipe-Symbol 250 PlainText 401 Point-Objekt 206 PopulateList-Methode 382 Popup-Ereignis 369 Präsentationsschicht 283 PreAuthenticate 427 PreferredColumnWidth 293 PreferredHeight-Eigenschaft 187 PreferredRowHeight 293 Primärschlüssel 316 –, PrimaryKey-Eigenschaft 331 –, SqlCommandBuilder-Objekt 330 PrimaryKey-Eigenschaft 331 PrimaryScreen-Eigenschaft 603 PrintDialog-Dialogfeld 235 PrintDialog-Dialogfeld 24 PrintDocument-Objekt 406 PrintDocument-Steuerelement 50
PrintPage-Ereignis 406 PrintPreviewControl-Steuerelement 51 PrintPreviewDialog-Dialogfeld 235, 248 PrintPreviewDialog-Steuerelement 256 PrintPreviewDialog-Steuerelement 40 PriorityBoostEnabled 701 PriorityClass 701 Priority-Eigenschaft 630 Private 48 PrivateMemorySize 701 PrivilegedProcessorTime 701 Process-Klasse 699 ProcessName 701 ProcessorAffinity 701 Profilerstellung 682 Profiling 19, 682, 696 –, Anwendungsstatistik 698 –, Begriff 696 –, Eigenschaften der Process-Klasse 700 –, Process.Refresh-Methode 699 –, Process-Klasse 696 –, Prozesse überwachen 697 –, Prozess-ID (PID) herausfinden 698 –, Task-Manager 696 Programmabsturz verhindern 683 Programmiersprache –, Begriff 27 –, C# 18, 27f., 260, 326 –, C++ 27 –, Einführung 27 –, Java 27 –, SQL 319 –, Visual Basic 27 ProgressBar-Steuerelement 221 ProgressBar-Steuerelement 52 property 240 PropertyGrid-Steuerelement 40 ProtocolVersion 429 Protokoll –, austauschbar 418 –, FTP 416 –, HTTP 416 –, Internet 416 –, plattformunabhängig 517 –, SOAP 519 –, Webdienste 520 Protokollmethode 427
829
Stichwortverzeichnis
Provider, verwaltete Proxy 427 Prozess 622 Public 48 Pulse 640 PulseAll 640 PushButton 133
86
Q Quellcode –, ActiveX Control Importer 481 –, C# 46 –, Editor 34 –, kompilieren 63 –, Visual Basic .NET 46 QueueUserWorkItem-Methode 645 Quicken 308 QuickInfo 221
R RadioButton-Steuerelement 180 RadioButton-Steuerelement 11 Random-Klasse 172 Reader 388 –, schließen 421 ReadLine-Methode 392 Read-Methode, SqlDataReader-Objekt ReadToEnd-Methode 392 ReadXml-Methode 342 Real 340 Rechteck 442 Recordset 312 Rectangle (Rechteck) 442 Rectangle-Objekt 83, 443, 593 ref 490 ReferenceEquals-Methode 81 Referenzen 255 –, vergleichen 81 Referer 429 Refresh-Methode 93 regasm.exe 499 Region (Bereich) 442 Region-Objekt 600 –, erzeugen 443 Registerkarte 17, 200 Registrierungs-Editor 657 Rekursion 383
830
337
RelationsCollection-Objekt 323 RemoveHandler-Methode 161 Request/Response-Modell 416 Request-Objekt 423 RequestUri 427 Reset 248, 471 ResetBackColor 93 ResetCursor 93 ResetCursor-Methode 448 ResetFont 94 ResetForeColor 94 ResetText 94 resgen.exe (Resource File Generator) 671 Resize 139 Resource File Generator 671 Responding 701 ResponseUri 428 Ressourcen –, Garbage Collection 32 –, Lokalisierung 669 –, Speicherbereinigung 32 Ressourcendatei 670 –, aus Bildern erzeugen 679 –, Kompilieren zu Assemblies 670 –, Satellitenassemblies 672 Ressourcenfallback 673 Rich Text 208 RichNoOleObjs 401 RichText 401 RichTextBox-Steuerelement 206, 400 –, Dock-Eigenschaft 210 –, Dokument drucken 408 –, Ereignis behandeln 212 –, Fokus 207, 266 –, Konstruktor 210 –, LoadFile-Methode 401 –, Mitglieder 215 –, NetWord 262 –, SaveFile-Methode 401 –, Schriftart 408 –, ScrollBars-Eigenschaft 210 –, SelectionFont-Eigenschaft 213 –, Text formatieren 208 –, Word-Dokument 488 RichTextBox-Steuerelement 55 RichTextBoxStreamType-Aufzählung 401 Root 387
Stichwortverzeichnis
Rotate 471 RotateAt 471 RotateTransform-Methode Round-trip-Format 303 RowHeadersVisible 293 RowHeaderWidth 293 RowStateFilter-Eigenschaft Run 56, 69, 544 Running 558, 630
470
342
S Satellitenassemblies 671 SaveFileDialog-Dialogfeld 235 SaveFileDialog-Dialogfeld 20 SaveFile-Methode 401 Scale 471 ScaleTransform-Methode 470 Schaltfläche –, Aussehen 179 –, Datenbindung 285 –, Ergebnis in Dialogfeld 245 –, FlatStyle-Eigenschaft 179 –, ImageList-Objekt 128 –, in Dialogfeld 233 –, Symbolleiste 127 Schemabezeichner 417 Schleifenformatbezeichner 303 Schlüssel/Wert-Paare 664 Schlüsselwort –, Begriff 48 –, delegate 169 –, event 164 –, Friend 48 –, Get 240 –, Handles 161 –, Imports 40, 49 –, Inherits 49 –, LIKE 320 –, Lock (in C#) 641 –, Me 58 –, NonInheritable 107 –, Overloads 62 –, Override 445 –, Private 48 –, property 240 –, Protected 478
–, Public 48, 478 –, ref 490 –, sealed 107 –, Set 240 –, Shared 54 –, static 54 –, SyncLock (in VB) 641 –, this 58 –, throw 688 –, using 49 –, WithEvents 69, 226 Schnittstelle IList 284 Schriftart 209 –, Antialiasing 450 –, DateTimePicker-Steuerelement 190 –, Font-Eigenschaft 84 –, FontFamily-Objekt 84 –, Font-Objekt 444 –, Kalender 190 –, NetWord 269 –, Schriftstil 210 –, Standarddialogfeld 252 –, Steuerelement 569 –, wechseln 155 Schriftfarbe –, NetWord 269 –, TabControl-Steuerelement 581 SCM 17 Screen-Klasse 603 Screen-Objekt 83 Scrollable 220 Scrollable Controls 33 ScrollBar-Klasse 44 ScrollBars 210 Scroll-Box (Bildlauffeld) 143 Scroll-Ereignis, Bildlaufleiste 141 SDI, Anwendung 350 sealed 107 Secure Sockets Layer (SSL), Begriff 433 Seite –, Einstellungen ändern 411 –, HasMorePages-Eigenschaft 410 –, PrintPageEventArgs-Objekt 411 Seiteneigenschaften 255 SELECT 319 –, WHERE 320
831
Stichwortverzeichnis
Selected 571 SelectedTab-Eigenschaft 205 SelectionBackColor 293 SelectionFont-Eigenschaft 213 SelectionForeColor 293 SendChunked 429 Sender-Objekt 158 SendToBack-Methode 93 Separator 115, 120 –, Symbolleiste 133 Serialisierung 396, 722 –, Begriff 396 –, Binärdaten 397 –, XML-Serialisierung 400 Server 417, 419 –, Webserver 419, 540 Service Control Manager (SCM) 542 –, benutzerdefinierter 559 ServiceAccount-Aufzählung 548 ServiceBase-Klasse –, Eigenschaften 543 –, Methoden 543 –, OnCustomCommand-Methode 559 ServiceController-Klasse –, Ereignisprotokoll 560 –, ExecuteCommand-Methode 559 ServiceControllerStatus-Aufzählung 558 ServiceName 543 ServicePoint 429 Services 17 Shared 54 SHDocVw.dll 482 Shear 471 Shift-Eigenschaft 103 Shortcut –, Menü 121 –, Symbolleiste 126 Shortcut-Eigenschaft 121 ShowChangeBox-Methode 242 ShowDialog-Methode 242 ShowHelp-Methode 248 ShowLines 220 Show-Methode –, MessageBox 235 –, Parameter 236
832
ShownInTaskBar-Eigenschaft 86 ShowPanels-Eigenschaft 135 ShowPlusMinus 220 ShowRootLines 220 Sicherheit 433 Sicherungsspeicher 379 Signatur, Ereignishandler 155 Simple Object Access Protocol (SOAP) 519 Single 340 Single Document Interface (SDI) 350 SingleBitPerPixel 450 SingleBitPerPixelGridFit 450 Singlethreaded-Umgebung 623 Sizable 87 SizableToolWindow 87 Size-Eigenschaft 83 SizeGripStyle 89 Size-Objekt 83 SmallChange-Eigenschaft 143 SmallDateTime 340 SmallDecrement 143 SmallIncrement 143 SmallInt 340 SmallMoney 340 SOAP 517 –, Begriff 519 –, Installer 552 SOAP (Simple Object Access Protocol) 519 Sortieren –, ASC (aufsteigend) 321 –, DESC (absteigend) 321 –, Excel und ActiveX 492 –, ORDER BY 321 –, Reihenfolge 321 –, Spalte 321 Sortierreihenfolge 342 Source 688 Spalte –, formatieren 297 –, sortieren 321 Speicher, Garbage Collection 32 Speichern, Dokument 510 Sperren –, Code 640 –, Enter-/Exit-Methoden 640 –, Objekt 640
Stichwortverzeichnis
–, Ressourcen 627 –, Syntax 641 –, Threading 636 Spring-Eigenschaft 135 SQL 310 –, Befehle generieren 330 –, Begriff 319 –, Datenbankabfrage 319 –, DELETE 323 –, gespeicherte Prozedur 338 –, INSERT 322 –, Join 323 –, LIKE 320 –, ORDER BY 321 –, SELECT 319 –, Union 323 –, UPDATE 322 –, VALUES 322 –, WHERE 320 SQL Server 19, 284, 310, 540 –, Datenbank anlegen 317 –, Datentypen 318 –, Enterprise Manager 338 –, gespeicherte Prozedur 338 –, Konto für Systemadministrator 327 –, Verbindungszeichenfolge 326 SQL Server-Authentifizierung 327 SqlCommandBuilder-Objekt 330 –, Primärschlüssel 330 SqlCommand-Objekt 335 SqlDataAdapter-Objekt 328 –, Daten bearbeiten 329 –, Eigenschaften 329 SqlDataReader 86 SqlDataReader-Objekt 335 –, Get-Methode 337 SQL-Provider 325 SSL 17 StackTrace 688 Standarddialogfeld 235, 248 –, ColorDialog-Steuerelement 250 –, Datei öffnen 248 –, Drucken 253 –, Farbe 250 –, FileName-Eigenschaft 248 –, Filterzeichenfolge 250
–, PrintPreviewDialog-Steuerelement 256 –, Schriftart 252 –, Seite einrichten 254 –, Speichern 248 Standarddialogfeld 19 StandardError 701 StandardInput 701 Standardnamensraum 51 StandardOutput 701 Standardzeichensatz 253 StartCap 454 StartFigure-Methode 457 StartInfo 701 StartPending 558 StartTime 701 State 570 static 54 Statusanzeige 52 StatusBar 133 StatusBarPanel 133 StatusBar-Steuerelement 58 Statusleiste 125, 133 –, AutoSize-Eigenschaft 135 –, NetWord 275 –, ShowPanels-Eigenschaft 135 –, Spring-Eigenschaft 135 –, StatusBar-Objekt 133 Statusleiste 58 Statusleistenbereich 133 Steuerelemente 112 –, ActiveX 480 –, ADO.NET 325 –, anpassen 566 –, Appearance-Eigenschaft 183 –, Architektur 592 –, Auf-Ab-Steuerelemente 192 –, Aussehen automatisch ändern 182 –, benutzerdefinierte 590 –, Benutzersteuerelemente 611, 613 –, Besitzer 581 –, besitzergezeichnete 568 –, Bezeichnungsfeld 178 –, bildlauffähige 33 –, Bildlaufleiste 136, 178 –, Bildlaufleiste 44 –, Button 178f., 633, 686
833
Stichwortverzeichnis –, Button 11 –, CheckBox 180 –, CheckBox 12 –, CheckChanged-Ereignis 183 –, CheckedListBox 18 –, CheckListBox 221 –, ColorDialog 20 –, ComboBox 178, 183, 287, 494, 567, 575 –, ComboBox 14 –, Containersteuerelement 114 –, Containersteuerelement 36 –, ContextMenu 209, 274 –, ContextMenu 25 –, Control-Klasse 592 –, DataGrid 221, 282, 290 –, DataGrid 28 –, DateTimePicker 189 –, DateTimePicker 32 –, Dialogfeld 239 –, DomainUpDown 35 –, Eigenschaften 594 –, Einbetten in Internet Explorer 430 –, Ereignisse 594 –, ErrorProvider 42 –, Erweiterung 611 –, Form 569 –, GDI+ 567 –, GroupBox 226 –, GroupBox 43 –, Gruppenfeld 226 –, gruppieren 182 –, Hintergrundfarbe 569 –, HScrollBar 45 –, ImageList 193 –, ImageList 45 –, in Windows Forms 592 –, kapseln 652 –, Kombinationsfeld 178, 183 –, Kontextmenü 262 –, Kontrollhäkchen 183 –, Label 178, 591, 633 –, Label 46 –, Layout 222 –, Layout in Windows Form 229 –, LinkLabel 46 –, ListBox 195, 494, 567
834
–, ListBox 16 –, Listen-Steuerelement 114 –, ListView 47 –, MainMenu 26 –, Menü 114, 178, 260, 262, 352, 566 –, Menüelement 260 Shortcut 274 –, MenuItem 209, 274 –, MenuItem 26 –, MessageBox 269 –, Methoden 594 –, neu zeichnen 597 –, NumericUpDown 39 –, Optionsfeld 178 –, Panel 140, 717 –, Panel 39 –, PictureBox 199, 437 –, PictureBox 50 –, PrintDocument 50 –, PrintPreviewControl 51 –, PrintPreviewDialog 40 –, ProgressBar 221 –, ProgressBar 52 –, PropertyGrid 40 –, RadioButton 178, 180 –, RadioButton 13 –, RichTextBox 206, 262, 400, 421, 685, 705 –, RichTextBox 55 –, Schaltfläche 178 –, Schaltflächen-Steuerelement 114 –, Schriftart 569 –, Standarddialogfeld 19 –, Statusanzeige 52 –, StatusBar 133, 275 –, StatusBarPanel 133, 275 –, StatusBarPanel 58 –, Statusleiste 178, 262, 275 –, Statusleiste 58 –, Strukturansicht 178 –, Symbolleiste 178 –, Symbolleiste 61 –, TabControl 200, 567 –, TabControl 59 –, TabPage 200 –, TabPage 42 –, TextBox 178, 183, 206, 593
Stichwortverzeichnis –, TextBox 57 –, Textfeld 53 –, Timer 178, 215, 591 –, Timer 61 –, ToolBar 61 –, ToolBarButton 62 –, ToolTip 221 –, ToolTip 63 –, TrackBar 64 –, TreeNode 67 –, TreeView 178, 218, 239, 283, 382 –, TreeView 65 –, Verlaufsleiste 64 –, Vordergrundfarbe 569 –, vorhandene Steuerelemente erweitern –, VScrollBar 717 –, VScrollBar 45 –, Webbrowser (nur ActiveX) 482 –, zeichnen 599 –, zusammengesetzte 611, 613 –, Zustand 570 Steuerschaltflächen 243 Stift 442 –, Linie zeichnen 455 Stileigenschaften 297 Stopped 558, 630 StopPending 558 StopRequested 630 Stream –, Begriff 379 –, Binärdaten lesen und schreiben 393 –, BinaryFormatter-Objekt 399 –, BinaryReader 393 –, BufferedStream 379 –, Close-Methode 390 –, CryptoStream 379 –, Datei lesen und schreiben 388 –, Datenquelle 379 –, FileStream 379 –, HtmlTextWriter 393 –, HttpWriter 393 –, in Web-Interaktion 422 –, MemoryStream 379 –, NetworkStream 379 –, Posting 425 –, Quelltyp 379
611
–, Reader 380 –, Sicherungsspeicher 379 –, StreamReader 392 –, StreamReader-Objekt 392 –, StreamWriter 390 –, StringReader 380, 393 –, StringWriter 393 –, verknüpfen 379 –, Writer 380 –, XmlReader 380 StretchImage 200 String 340 StringReader 380 Strom 17 –, StreamReader-Objekt 701 –, StreamWriter-Objekt 701 –, StringReader-Objekt 511 Structured Query Language (SQL) 319 Strukturansicht 17, 218 Strukturknoten 221 Style-Eigenschaft 132 Sub 54 Suchanwendung, multithreaded 642 Suchen, Dialogfeld 272 Suspended 630 SuspendRequested 630 Symbolleiste 125f. –, anpassen 131 –, Appearance-Eigenschaft 131 –, Begrenzung 132 –, Eigenschaften 131 –, Handler 130 –, Icon-Eigenschaft 135 –, ImageList-Objekt 128 –, Schaltfläche 127 –, Schaltflächenstil 133 –, Steuerelemente 61 –, Trennlinie 132 Synchronisierung 635 SyncLock 641 Syntax der Eigenschaftendeklaration 595 System (Namensraum) 50 Systemadministrator 327 SystemDefault 450 Systemmenüfelder 89 Systemprotokoll 555
835
Stichwortverzeichnis
T TabControl-Steuerelement 200 –, AddTab-Methode 205 –, anpassen 578 –, Appearance-Eigenschaft 206 –, Aussehen 206 –, ChangeLocation-Methode 205 –, Eigenschaften 205 –, Hintergrundfarbe 581 –, Padding-Eigenschaft 206 –, Schriftfarbe 581 TabControl-Steuerelement 59 Tabelle –, benutzerdefinierte Ansicht 341 –, Beziehungen 323 –, DataGrid-Steuerelement 298 –, DataTable-Objekt 323 –, Datenblatt 298 –, Datenblattansicht 314 –, Entwurfsansicht 314 –, RelationsCollection-Objekt 323 –, TableStyles-Eigenschaft 298 –, Zeile aktualisieren 322 –, Zeile einfügen 322 –, Zeile löschen 323 TabPage-Steuerelement 200 TabPage-Steuerelement 42 Tabseite 17, 200 –, HotTrack-Eigenschaft 206 –, Label-Steuerelement 203 –, Multiline-Eigenschaft 206 Tabulatorzeichen, Listenfeld 198 TargetSite 688 Taschenrechner 499 Taschenrechner-Anwendung 175 Task-Manager 696 Tastaturereignisse 101 Tastenkombination 117 –, NetWord 274 –, später festlegen 122 tblimp.exe (Type Library Importer) 486 TCP 416 Temperaturumrechner 233 Text 340 –, DrawString-Methode 444 –, zeichnen 444, 446
836
TextBoxBase-Klasse 53 TextBox-Steuerelement 206 –, Datenbindung 333 –, Dialogfeld 239 –, Einbetten in Browser 430 –, Eingabe maskieren 207 –, Fokus 207 –, Handle 593 –, Methoden 207 TextBox-Steuerelement 57 Text-Eigenschaft 84 –, ListBox-Steuerelement 199 Textfarbe 84 Textfeld –, DataGridTextBoxColumn 297 –, rückgängig machen 207 –, Steuerelemente 53 Textprogramm 34 TextRenderingHint-Eigenschaft 450 TextTextOleObjs 401 Textverarbeitung 261 Thermostat 619 this 58 Thread 696 –, Begriff 622 –, Ereignishandler 631 –, erzeugender 652 –, Hauptthread 627, 650 –, Hintergrundthread 629 –, Join-Methode 633 –, Marshalling (eines Befehls) 634 –, Priorität 630 –, Priority-Eigenschaft 630 –, Profiling 702 –, Thread-Objekte 701 –, threadsicher 635 –, ThreadState-Eigenschaft 630 –, untersuchen 628 –, Vordergrundthread 629 –, Zustand 630 Threading 622 –, Datennutzung 626 –, Klasse 627 –, Kommunikation 626 –, Monitor-Klasse (Synchronisierung) 639 –, Multiprozessorsysteme 653
Stichwortverzeichnis
–, Mutex-Klasse 644 –, Probleme mit 626 –, ReaderWriterLock-Klasse 644 –, Ressourcenbedarf 626 –, Ressourcensperrung 627 –, Sperren 636 –, statische Threadvariablen 653 –, Synchronisierung 635 –, Threadpooling 628 Threadpool 645 –, Threads benachrichtigen 649 Threadpooling 645 Threads 701 ThreadStart-Delegat 631 ThreadState-Eigenschaft 630 threadübergreifendes Marshalling 637 throw 688 ThumbPosition 143 ThumbTrack 143 Tilde 59 Timeout 427 Timer-Steuerelement 215 –, Einsatzmöglichkeiten 215 61 Timer-Steuerelement Timestamp 340 Time-Steuerelement, Profiling 698 TinyInt 340 ToggleButton 133 ToolBar 126 ToolBarButton-Objekt 126 ToolBarButton-Steuerelement 61 ToolTip-Steuerelement 221 63 ToolTip-Steuerelement Top-Eigenschaft 84 TopNode 220 ToString-Methode 73, 82 TotalProcessorTime 701 TrackBar-Steuerelement 64 TransferEncoding 429 Transform 454 Transformation –, Grafik 453 –, Graphics-Objekt 470 Transform-Methode 470 Translate 471 TranslateTransform-Methode 470 TransparencyKey-Eigenschaft 88
TreeNode-Objekt 218 TreeNode-Steuerelement 67 TreeView-Steuerelement 17, 218 –, aktualisieren 402 –, anpassen 219 –, Eigenschaften 219 –, Ereignisse 221 –, Methoden 221 –, Stammknoten (root node) 218 –, Strukturknoten (node) 218 –, TreeNode-Objekt 218 –, Verzeichnis durchlaufen 381 TreeView-Steuerelement 65 Treffertest 305 Trennlinie 115, 120 –, Symbolleiste 132 Treppcheneffekt 449 Truncate 390 TryEnter 640 Typbibliothek –, Begriff 485 –, Excel 491 –, excel9.olb 491 –, Microsoft Office 485 –, msword9.olb 486 –, Type Library Importer 486 –, Word 486 –, Wrapper 486 Typografie 441 Typumwandlung 73, 80 –, Casting-Operator 163
U UDDI 534 UDP 416 Überladen 60 UI (User Interface) 17 UnicodePlainText 401 Uniform Resource Locator (URL) 417 Union 323 UniqueIdentifier 340 Universal Description, Discovery and Integration (UDDI) 534 Universal Resource Identifier (URI) 417 Unstarted 630 UPDATE 322 UpdateClock-Methode 217
837
Stichwortverzeichnis
Update-Methode 603 URI 417 –, HTTPS 433 –, Schemabezeichner 417 URL 417 –, Abfragezeichenfolge 418 –, Path Identifier 418 –, Pfadbezeichner 418 –, Schema Identifier 417 –, Schemabezeichner 417 –, Webbrowser 484 urlencoded 425 User Interface (UI) 68 UserAgent 429 User-Konto 548 UserProcessorTime 702 using 49
V Validated-Ereignis 100 VALUES 322 VarBinary 340 VarChar 340 Variable 60, 209, 683 –, beobachten 694 –, deklarieren 69 –, Objekt zuweisen 70 –, Option Strict 79 –, Text-Eigenschaft 168 –, threadübergreifend nutzen 637 –, Typ deklarieren 79 –, unter GDI+ 468 –, vergleichen 81 Variant 340 VB .NET 18, 28, 58 Vektorgrafik 441 Verarbeitungsregeln 283 Verbindungszeichenfolge 326 –, Access 326 –, OleDBConnection-Objekt 326 –, SQL Server 326 –, SqlConnection-Objekt 326 Vergleichen, Zeichenfolge 66 Verlaufsleiste 64 Version, Konfiguration 665 verwaltete Provider 86
838
verwalteter Code 32 Verzeichnis –, durchlaufen 381 –, überwachen 402 Verzeichnisstruktur 218 Verzerren, Grafik 451 VIEWASTEXT 431 VirtualMemorySize 702 Visible 122 VisibleCount 220 Visible-Eigenschaft 88 Visual Basic 27 –, OLE-Automatisierung 479 Visual Basic-Editor 497 Visual C++, OLE-Automatisierung 479 Visual Studio .NET 18, 34, 351, 693 –, kompilieren 63 –, Lokalisierung 671 –, Ressourcendateien 671 Vordergrundfarbe 569 Vordergrundthread 629 VS .NET 34 –, Projektmappen-Explorer 64 –, Quellcode-Editor 64 –, Windows Forms-Designer 65 VScrollBar-Steuerelement 45
W Wait 640 WaitCallBack-Delegat 645 WaitSleepJoin 630 Warteschlange 648 Web Service (Webdienst) 37 Web Service Discovery-Werkzeug 527 Web Services Description Language (WSDL) 525 Webanforderungen, asynchrone 436 Webbrowser, ActiveX-Steuerelement 482 Webdienst 436, 516 –, .asmx 523 –, Bedeutung und Anwendungen 517 –, Begriff 30 –, Brinkster 537 –, Dateierweiterung 523 –, Dienstbeschreibung 524f. –, Discovery 526
Stichwortverzeichnis
–, Ermitteln 526 –, Funktionsweise 521 –, in Anwendungen 526 –, Konsumieren 528 –, kostenlos testen 537 –, Namensraum 523 –, Protokolle 520 –, Proxy-Klasse 528 –, Richtlinien für Einsatz 535 –, suchen 534 –, UDDI 534 –, Webmethode 523 –, WSDL 525 Web-Posting 422 WebRequest 418 –, Anforderungen senden 419 WebRequest-Objekt 416 –, Eigenschaften 426 –, HttpWebRequest 428 –, Methoden 426 –, schließen 421 WebResponse 418, 427 Webserver, Webdienst kostenlos testen 537 Weckuhr 216, 232 WHERE 320 Width 454 Wiedergabehinweis, Antialiasing 450 Wiederverwendbarkeit 591 Win16 28 Win32 28 Windows –, Betriebssystem 18 –, Ein-/Ausgabe 378 –, Grafikfähigkeiten 440 –, Registrierung 499, 503, 657 –, Systemmonitor 703 –, Task-Manager 696 Windows 2000 18, 33 –, Dienste 541 Windows Forms 35 –, automatisches Layout 178 –, Dialogfeld 232 –, Einführung 35 –, Ereignisse 94, 152 –, Fehlerbehandlung 666 –, Funktionen 36
–, mit Steuerelementen erweitern 177 –, Steuerelemente 37 –, Technologie 17 Windows Forms-Anwendung –, C# 47 –, erstellen 45 –, Implementierung 37 –, Konfiguration 37 –, VB .NET 46 Windows Forms-Designer 64 Windows Forms-Steuerelemente 68, 112 –, Architektur 38 –, Control-Klasse 592 Windows Installer 678 Windows NT 4.0 18, 33 Windows Task-Manager 704 Windows XP 18, 33 –, Konto 548 Windows-Authentifizierung 327 Windows-Betriebssystem 26, 96 Windows-Dienste 540 –, Begriff 540 –, Benutzeroberfläche 544 –, deinstallieren 552 –, Dienststeuerung 556 –, Einsatz 541 –, erzeugen 542 –, Installer 547 –, Konto 548 –, Schnittstellen 555 –, ServiceBase-Klasse 542 –, ServiceController-Klasse 555 –, starten/beenden 552 –, Status 558 –, überwachen 553 Windows-Diensteverwaltung 17, 542 Windows-Editor 18, 705 Windows-Programmierung 26 WithEvents 69, 226 Word 232, 261, 474, 518 –, Auflistung von Dokumenten 489 –, Dokumente bearbeiten 412 –, Ergebnis eines Dialogfeldes 246 –, Menü 352 –, NetWord 705 –, Prozesse 624
839
Stichwortverzeichnis
–, Rechtschreibprüfung 635 –, Typbibliothek 486 –, via ActiveX nutzen 486 WordPad 18 WorkingSet 702 Wrapper, Typbibliothek 486 WriteLine-Methode 390 Write-Methode 390 Writer 388 –, Close-Methode 390 –, schließen 426 WSDL 525 –, Parameter 529 –, Proxy-Klasse 528 –, wsdl.exe 528 WWW Consortium 429
X XCOPY-Befehl 677 XML 310, 342, 422, 517 –, ADO.NET 313 –, Bedeutung 517 –, GetXml-Methode 342 –, ReadXml-Methode 342 –, SOAP 517 –, WSDL 525 –, XML-Schema 313 XmlDataDocument-Objekt 344 XmlReader-Objekt 335
Z Zeichenabstand 253 Zeichenfolge –, ausgeben 80 –, Eingaben umwandeln –, Filter 249 –, Format 191
840
71
–, formatieren 303 –, Größe messen 585 –, Kombinationsfeld 184 –, Listenelement 189 –, Schriftart wechseln 155 –, Statusleistenbereich 135 –, suchen 215 –, Titelzeile des Formulars 243 –, Variablentyp 82 –, Verbindungszeichenfolge 326 –, vergleichen 66 –, zeichnen 444 Zeichnen –, benutzerdefinierter Modus 578 –, DrawMode-Eigenschaft 568 –, Farbverlauf 461 –, Fenster 443 –, Figur füllen 442 –, GraphicsPath-Objekt 456 –, Linie 442 –, Steuerelemente 599 –, Text 444 Zeile –, aktualisieren 322 –, einfügen 322 –, löschen 323 Zeilenabschlusszeichen 390 Zeilenumbruch 207 Zeit –, aktuelle 217 –, DateTime.Now-Methode 217 Zufallszahlen (Random-Klasse) 172 Zugriffstaste 117 Zustand –, Steuerelement 570 –, Thread 630
Bonuskapitel 1: Steuerelemente in Windows Forms
2
Bonuskapitel 1
Dieser Anhang befasst sich mit den Eigenschaften und Methoden jedes Windows FormsSteuerelements. Er beschreibt sogar einige Steuerelemente, die im technischen Sinne keine Windows Forms-Steuerelemente sind, sondern ihnen lediglich Unterstützung bieten.
1.1
Die Klasse Control
Alle Steuerelemente in diesem Anhang (wenn nicht anders angegeben) erben die in Tabelle B.1 beschriebenen Eigenschaften von der System.Windows.Forms.Control-Klasse. Die Control-Klasse ist die wichtigste, die man sich merken muss, da sie für alle anderen Steuerelemente verantwortlich ist. Eigenschaft
Beschreibung
AccessibilityObject
Liefert das AccessibleObject-Objekt für das Steuerelement.
AccessibleDefaultActionDescription
Die Zeichenfolge, die benutzt wird, um die Standardaktion eines Steuerelements zu beschreiben.
AccessibleDescription
Die Beschreibung des Steuerelements, die von Eingabehilfenclients verwendet wird.
AccessibleName
Der Name des Steuerelements, der von Eingabehilfenclients benutzt wird.
AccessibleRole
Die Rolle (oder der Typ) des Steuerelements; verwendet Werte aus der AccessibleRole-Aufzählung.
AllowDrop
Gibt an, ob das Steuerelement Drag & Drop-Daten akzeptieren kann.
AllowDropAnchor
Gibt die Ränder des Steuerelements an, die am umgebenden Steuerelement verankert sind.
AccessibleRoleBackColor
Die Hintergrundfarbe des Steuerelements.
BackgroundImage
Die Grafik, die im Hintergrund des Steuerelements angezeigt werden soll.
BindingContext
Der BindingContext des Steuerelements (wird für Datenbindung verwendet).
Bottom
Der Abstand zwischen dem unteren Rand des Steuerelements und dem oberen Rand seines umgebenden Steuerelements.
Tabelle 1.1: Eigenschaften der Control-Klasse, die alle Steuerelemente erben
2
Bonuskapitel 1
Eigenschaft
Beschreibung
Bounds
Ein Rectangle, das die Größe und Position des Steuerelements darstellt.
CanFocus
Gibt an, ob das Steuerelement den Fokus erhalten kann.
CanSelect
Gibt an, ob das Steuerelement ausgewählt werden kann.
Capture
Gibt an, ob das Steuerelement die Maus »eingefangen« hat.
CausesValidation
Gibt an, ob das Steuerelement Gültigkeitsprüfungen ausführen soll, wenn es den Fokus erhält.
ClientRectangle
Ein Rectangle, das den Clientbereich des Steuerelements (nicht zwingend den gleichen wie Bounds) darstellt.
ClientSize
Die Höhe und Breite des Clientbereichs des Steuerelements.
CompanyName
Der Name des Unternehmens oder des Entwicklers des Steuerelements oder seiner umgebenden Anwendung.
ContainsFocus
Gibt an, ob das Steuerelement oder eines seiner untergeordneten Steuerelemente den aktuellen Fokus besitzt.
ContextMenu
Ein ContextMenu-Objekt, das mit diesem Steuerelement verknüpft ist.
Controls
Liefert ein ControlCollection-Objekt, das alle untergeordneten Steuerelemente des angegebenen Steuerelements repräsentiert.
Created
Gibt an, ob das Steuerelement erstellt worden ist.
Cursor
Gibt den Typ des Mauscursors an, der benutzt wird, wenn sich der Mauszeiger über dem Steuerelement befindet.
DataBindings
Liefert die Datenbindungen für das Steuerelement.
DefaultBackColor
Die Standardhintergrundfarbe eines Steuerelements.
DefaultFont
Die Standardschriftart eines Steuerelements.
DefaultForeColor
Die Standardvordergrundfarbe (normalerweise die Schriftfarbe) eines Steuerelements.
DisplayRectangle
Ein Rectangle-Objekt, das die Anzeigefläche des Steuerelements repräsentiert.
Disposing
Gibt an, ob das Steuerelement gerade freigegeben wird.
Tabelle 1.1: Eigenschaften der Control-Klasse, die alle Steuerelemente erben (Forts.)
3
Bonuskapitel 1
Eigenschaft
Beschreibung
Dock
Gibt an, an welchem Rand des übergeordneten Containers das Steuerelement angedockt ist.
Enabled
Gibt an, ob das Steuerelement auf Benutzereingaben reagieren kann.
Focused
Gibt an, ob das Steuerelement den Eingabefokus besitzt.
Font
Die im Steuerelement verwendete Schriftart.
ForeColor
Die Vordergrundfarbe (normalerweise die Schriftfarbe) des Steuerelements.
Handle
Liefert den Fensterhandle, an den das Steuerelement gebunden ist.
HasChildren
Gibt an, ob das Steuerelement untergeordnete Steuerelemente enthält.
Height
Die Höhe des Steuerelements.
ImeMode
Der Editormodus für die Eingabemethode des Steuerelements (erlaubt es Ihnen, fremdsprachige Zeichen und Symbole auf einer Standardtastatur einzugeben).
InvokeRequired
Gibt an, dass der Aufrufer dieses Steuerelements nicht direkt mit ihm interagieren kann, weil es sich in einem anderen Thread befindet (anders ausgedrückt: ein Invoke-Methodenaufruf ist erforderlich).
IsAccessible
Gibt an, ob das Steuerelement für Eingabehilfenclients sichtbar ist.
IsDisposed
Gibt an, ob das Steuerelement bereits freigegeben wurde.
IsHandleCreated
Gibt an, ob der Handle des Steuerelements bereits erzeugt worden ist (normalerweise wenn das Steuerelement am Bildschirm angezeigt wird).
Left
Der linke Rand des Steuerelements in Bildpunkten, bezogen auf den linken Rand des umgebenden Steuerelements.
Location
Ein Point-Objekt, das die obere linke Ecke des Steuerelements festlegt, bezogen auf die obere linke Ecke seines umgebenden Steuerelements.
ModifierKeys
Gibt an, welche (falls überhaupt) der Modifizierertasten ((ª), (Strg) oder (Alt)) gedrückt worden sind, als das Steuerelement aktiviert wurde.
MouseButtons
Gibt an, welche der Maustasten gedrückt wurde, als das Steuerelement aktiviert wurde.
MousePosition
Gibt die Position des Mauszeigers an.
Tabelle 1.1: Eigenschaften der Control-Klasse, die alle Steuerelemente erben (Forts.)
4
Bonuskapitel 1
Eigenschaft
Beschreibung
Name
Der Name des Steuerelements.
Parent
Liefert den übergeordneten Container des Steuerelements in der UI-Hierarchie.
ProductName
Der Produktname der Assembly, die das Steuerelement enthält.
ProductVersion
Die Version der Assembly, die das Steuerelement enthält.
RecreatingHandle
Gibt an, ob das Steuerelement gerade seinen Handle erneuert.
Region
Der Fensterbereich, der dem Steuerelement zugeordnet ist.
Right
Der Abstand zwischen dem rechten Rand des Steuerelements und dem linken Rand seines umgebenden Steuerelements.
RightToLeft
Gibt an, ob das Steuerelement Sprachen akzeptieren soll, die von rechts nach links geschrieben werden.
Site
Legt die Site des Steuerelements fest.
Size
Ein Size-Objekt, das Höhe und Breite des Steuerelements festlegt.
TabIndex
Die Aktivierreihenfolge dieses Steuerelements in seinem umgebenden Steuerelement.
TabStop
Gibt an, ob Benutzer diesem Steuerelement den Fokus verleihen können, indem sie die (ÿ_)-Taste drücken.
Tag
Das Objekt, das Daten über dieses Steuerelement enthält.
Text
Der Text, der mit diesem Steuerelement verknüpft ist (hat oft eine andere Bedeutung auf unterschiedlichen Steuerelementen).
Top
Die y-Koordinate des oberen Randes des Steuerelements, angegeben in Bildpunkten.
TopLevelControl
Gibt das oberste umgebende Steuerelement des aktuellen Steuerelements an.
Visible
Legt fest, ob das Steuerelement auf der Seite wiedergegeben werden soll.
Width
Die Breite des Steuerelements in Bildpunkten.
Tabelle 1.1: Eigenschaften der Control-Klasse, die alle Steuerelemente erben (Forts.)
5
Bonuskapitel 1
Tabelle B.2 führt die Methoden auf, die allen Steuerelemente gemeinsam sind, da sie ja vom Control-Objekt geerbt wurden. Methode
Beschreibung
BeginInvoke
Führt einen Delegaten asynchron aus.
BringToFront
Bringt das Steuerelement an den Anfang der z-Reihenfolge in seinem übergeordneten Steuerelement.
Contains
Gibt an, ob das aktuelle Steuerelement das angegebene Steuerelement enthält.
CreateControl
Erzwingt das Erstellen des Steuerelements.
CreateGraphics
Erzeugt ein Graphics-Objekt, das auf dem Steuerelement basiert.
DoDragDrop
Beginnt eine Drag & Drop-Operation.
EndInvoke
Gibt die gelieferten Werte der asynchronen Operation zurück, die mit BeginInvoke gestartet wurde.
FindForm
Liefert das Formular, auf dem sich das Steuerelement befindet.
Focus
Setzt den Eingabefokus auf das Steuerelement.
FromChildHandle
Liefert das Steuerelement, das den angegebenen Handle enthält.
FromHandle
Liefert das Steuerelement vom angegebenen Handle.
GetChildAtPoint
Liefert das untergeordnete Steuerelement, das sich am angegebenen Punkt befindet.
GetContainerControl
Liefert das umgebende Steuerelement des Steuerelements.
GetNextControl
Liefert das nächste Steuerelement in der Aktivierreihenfolge des umgebenden Steuerelements.
Hide
Verbirgt das Steuerelement vor dem Benutzer (man beachte, dass dies nicht notwendigerweise die Funktionsweise des Steuerelements beeinträchtigt).
Invalidate
Zwingt das Steuerelement dazu, einen bestimmten Bereich neu zu zeichnen.
Invoke
Führt einen Delegaten auf dem Erstellungsthread des Steuerelements aus.
IsMnemonic
Bestimmt, ob das angegebene Zeichen das mnemonische Zeichen ist, das mit dem Steuerelement verknüpft ist.
Tabelle 1.2: Methoden der Control-Klasse, die alle Steuerelemente erben
6
Bonuskapitel 1
Methode
Beschreibung
PerformLayout
Zwingt das Steuerelement dazu, auf seine untergeordneten Steuerelemente Layoutregeln anzuwenden.
PointToClient
Konvertiert den angegebenen Bildschirmpunkt in Clientkoordinaten.
PointToScreen
Konvertiert den angegebenen Clientpunkt in Bildschirmkoordinaten.
PreProcessMessage
Veranlasst das Steuerelement dazu, Eingabemeldungen vorab zu verarbeiten, bevor sie an die Standardfunktionen weitergeleitet werden.
RectangleToClient
Berechnet Größe und Position des Bildschirmrechtecks in Clientkoordinaten.
RectangleToScreen
Berechnet Größe und Position des Clientrechtecks in Bildschirmkoordinaten.
Refresh
Zwingt das Steuerelement dazu, sich selbst sowie alle untergeordneten Steuerelemente neu zu zeichnen.
ResetBackColor
Setzt die BackColor-Eigenschaft auf ihren Standardwert zurück.
ResetBindings
Setzt die DataBindings-Eigenschaft auf ihren Standardwert zurück.
ResetCursor
Setzt die Cursor-Eigenschaft auf ihren Standardwert zurück.
ResetFont
Setzt die Font-Eigenschaft auf ihren Standardwert zurück.
ResetForeColor
Setzt die ForeColor-Eigenschaft auf ihren Standardwert zurück.
ResetImeMode
Setzt die ImeMode-Eigenschaft auf ihren Standardwert zurück.
ResetRightToLeft
Setzt die RightToLeft-Eigenschaft auf ihren Standardwert zurück.
ResetText
Setzt die Text-Eigenschaft auf ihren Standardwert zurück.
ResumeLayout
Nimmt normale Layoutregeln nach einem Aufruf von SuspendLayout wieder auf.
Scale
Skaliert das Steuerelement und alle untergeordneten Steuerelemente.
Select
Aktiviert ein Steuerelement.
SelectNextControl
Aktiviert das nächste Steuerelement in der Aktivierreihenfolge des umgebenden Steuerelements.
SendToBack
Sendet das Steuerelement an das Ende der z-Reihenfolge.
SetBounds
Setzt die Grenzen des Steuerelements fest.
Tabelle 1.2: Methoden der Control-Klasse, die alle Steuerelemente erben (Forts.)
7
Bonuskapitel 1
Methode
Beschreibung
Show
Zeigt das Steuerelement dem Benutzer an.
SuspendLayout
Setzt vorübergehend die Layoutregeln für das Steuerelement aus; nimmt sie mit der ResumeLayout-Methode wieder auf.
ToString
Liefert eine Zeichenfolge, die das Steuerelement repräsentiert.
Update
Veranlasst das Steuerelement dazu, in seinem Clientbereich ungültige Bereiche zu zeichnen.
Tabelle 1.2: Methoden der Control-Klasse, die alle Steuerelemente erben (Forts.)
Tabelle B.3 führt die Ereignisse auf, die alle Windows Forms-Steuerelemente miteinander teilen, da sie vom Control-Objekt geerbt werden. Ereignis
Beschreibung
BackColorChanged
Wird ausgelöst, wenn sich die BackColor-Eigenschaft ändert.
BackgroundImageChanged
Wird ausgelöst, wenn sich die BackgroundImage-Eigenschaft ändert.
BindingContextChanged
Wird ausgelöst, wenn sich die BindingContext-Eigenschaft ändert.
CausesValidationChanged
Wird ausgelöst, wenn sich die CausesValidation-Eigenschaft ändert.
ChangeUICues
Wird ausgelöst, wenn sich der Fokus-Cue oder der Tastatur-Cue der Benutzeroberfläche ändert.
Click
Wird ausgelöst, wenn das Steuerelement angeklickt wird.
ContextMenuChanged
Wird ausgelöst, wenn sich die ContextMenu-Eigenschaft ändert.
ControlAdded
Wird ausgelöst, wenn ein Steuerelement der Controls-Auflistung dieses Steuerelements hinzugefügt wird.
ControlRemoved
Wird ausgelöst, wenn ein Steuerelement aus der Controls-Auflistung dieses Steuerelements entfernt wird.
CursorChanged
Wird ausgelöst, wenn sich die Cursor-Eigenschaft ändert.
DockChanged
Wird ausgelöst, wenn sich die Dock-Eigenschaft ändert.
DoubleClick
Wird ausgelöst, wenn auf das Steuerelement doppelgeklickt wird.
Tabelle 1.3: Ereignisse der Control-Klasse, die alle Steuerelemente erben
8
Bonuskapitel 1
Ereignis
Beschreibung
DragDrop
Wird ausgelöst, wenn eine Drag & Drop-Operation beendet wird.
DragEnter
Wird ausgelöst, wenn ein Objekt in die Grenzen des Steuerelements gezogen wird.
DragLeave
Wird ausgelöst, wenn ein Objekt über die Grenzen des Steuerelements hinausgezogen wird.
DragOver
Wird ausgelöst, wenn ein Objekt über das Steuerelement gezogen wird.
EnabledChanged
Wird ausgelöst, wenn sich die Enabled-Eigenschaft ändert.
Enter
Wird ausgelöst, wenn das Steuerelement betreten wird.
FontChanged
Wird ausgelöst, wenn sich die Font-Eigenschaft ändert.
ForeColorChanged
Wird ausgelöst, wenn sich die ForeColor-Eigenschaft ändert.
GiveFeedback
Tritt während eines Ziehvorgangs auf.
GotFocus
Wird ausgelöst, wenn das Steuerelement den Eingabefokus erhält.
HandleCreated
Wird ausgelöst, nachdem für das Steuerelement ein Handle erzeugt wurde.
HandleDestroyed
Wird ausgelöst, wenn der Handle des Steuerelements gerade gelöscht wird.
HelpRequested
Wird ausgelöst, wenn der Benutzer Hilfe zu einem Steuerelement anfordert.
ImeModeChanged
Wird ausgelöst, wenn sich die ImeMode-Eigenschaft ändert.
Invalidated
Wird ausgelöst, wenn die grafische Oberfläche eines Steuerelements erfordert, dass sie neu gezeichnet wird.
KeyDown
Wird ausgelöst, wenn eine Taste gedrückt wird, während das Steuerelement den Fokus hat.
KeyPress
Tritt sofort nach dem KeyDown-Ereignis auf.
KeyUp
Tritt sofort nach dem KeyUp-Ereignis auf.
Layout
Wird ausgelöst, falls ein Steuerelement seine untergeordneten Steuerelemente neu anordnen sollte.
Leave
Wird ausgelöst, wenn das Steuerelement den Eingabefokus verliert.
LocationChanged
Wird ausgelöst, wenn sich die Location-Eigenschaft ändert.
LostFocus
Wird ausgelöst, wenn das Steuerelement den Fokus verliert.
Tabelle 1.3: Ereignisse der Control-Klasse, die alle Steuerelemente erben (Forts.)
9
Bonuskapitel 1
Ereignis
Beschreibung
MouseDown
Wird ausgelöst, wenn eine Maustaste über dem Steuerelement gedrückt wird.
MouseEnter
Wird ausgelöst, wenn der Mauszeiger in den Bereich des Steuerelements eintritt.
MouseHover
Wird ausgelöst, wenn der Mauszeiger über dem Steuerelement schwebt.
MouseLeave
Wird ausgelöst, wenn der Mauszeiger das Steuerelement wieder verlässt.
MouseMove
Wird ausgelöst, wenn der Mauszeiger über dem Steuerelement bewegt wird.
MouseUp
Wird ausgelöst, wenn der Mauszeiger sich über einem Steuerelement befindet und eine Maustaste losgelassen wird (man beachte, dass die Maustaste nicht notwendigerweise über der Schaltfläche gedrückt worden sein muss).
MouseWheel
Wird ausgelöst, wenn sich das Mausrad bewegt, während das Steuerelement den Fokus hat.
Move
Wird ausgelöst, wenn das Steuerelement verschoben wird.
Paint
Wird ausgelöst, wenn das Steuerelement neu gezeichnet wird.
ParentChanged
Wird ausgelöst, wenn sich die Parent-Eigenschaft ändert.
QueryAccessibility Help
Wird ausgelöst, wenn die AccessibleObject-Eigenschaft Eingabehilfenclients Hilfe gewährt.
QueryContinueDrag
Tritt während einer Drag & Drop-Operation auf und erlaubt es der Quelle des Vorgangs zu bestimmen, ob die Drag & Drop-Operation abgebrochen werden soll.
Resize
Wird ausgelöst, wenn das Steuerelement in seiner Größe verändert wird.
RightToLeftChanged
Wird ausgelöst, wenn sich die RightToLeft-Eigenschaft ändert.
SizeChanged
Wird ausgelöst, wenn sich die Size-Eigenschaft ändert.
StyleChanged
Wird ausgelöst, wenn sich das Layout des Steuerelements ändert.
SystemColorsChanged
Wird ausgelöst, wenn sich die Systemfarben ändern.
TabIndexChanged
Wird ausgelöst, wenn sich die TabIndex-Eigenschaft ändert.
TabStopChanged
Wird ausgelöst, wenn sich die TabStop-Eigenschaft ändert.
TextChanged
Wird ausgelöst, wenn sich die Text-Eigenschaft ändert.
Tabelle 1.3: Ereignisse der Control-Klasse, die alle Steuerelemente erben (Forts.)
10
Bonuskapitel 1
Ereignis
Beschreibung
Validated
Wird ausgelöst, wenn das Steuerelement die Überprüfung abgeschlossen hat.
Validating
Wird ausgelöst, wenn das Steuerelement mit der Überprüfung beginnt.
VisibleChanged
Wird ausgelöst, wenn sich die Visible-Eigenschaft ändert.
Tabelle 1.3: Ereignisse der Control-Klasse, die alle Steuerelemente erben (Forts.)
1.2
Die Steuerelemente Button, CheckBox und RadioButton
Tabelle B.4 führt die Eigenschaften der ButtonBase-Klasse auf, die von den Button-, CheckBox- und RadioButton-Steuerelementen geerbt werden. Eigenschaft
Beschreibung
FlatStyle
Gibt die flache Darstellungsform des Steuerelements an; verwendet Werte aus der FlatStyle-Aufzählung.
Image
Die Grafik, die im Steuerelement angezeigt wird.
ImageAlign
Die Ausrichtung der Grafik, die im Steuerelement angezeigt wird.
ImageIndex
Der Grafiklistenindex der aktuell im Steuerelement angezeigten Grafik.
ImageList
Ein ImageList-Objekt, das ein Array von Grafiken enthält, die mit dem Steuerelement verknüpft sind.
TextAlign
Die Ausrichtung des im Steuerelement angezeigten Textes.
Tabelle 1.4: Eigenschaften des ButtonBase-Steuerelements
Das Steuerelement Button Das Button-Steuerelement repräsentiert eine anklickbare Schaltfläche in einem Windows Form. Tabelle B.5 führt Eigenschaft und Methode der Button-Klasse auf.
11
Bonuskapitel 1
Eigenschaft
Beschreibung
DialogResult
Ein Wert, der dem umgebenden Steuerelement geliefert wird, wenn die Schaltfläche angeklickt wird.
Methode
Beschreibung
PerformClick
Erzeugt ein Click-Ereignis für die Schaltfläche.
Tabelle 1.5: Eigenschaft und Methode der Button-Klasse
Das Steuerelement CheckBox Das CheckBox-Steuerelement repräsentiert ein Feld, das sich ein- oder ausschalten lässt, um die jeweilige Option zu aktivieren bzw. zu deaktivieren. Tabelle B.6 führt die Eigenschaften und Ereignisse der CheckBox-Klasse auf. Eigenschaft
Beschreibung
Appearance
Verwendet einen Wert aus der Appearance-Aufzählung, um die Darstellung des CheckBox-Steuerelements festzulegen.
AutoCheck
Gibt an, ob sich die Darstellung des CheckBox-Steuerelements ändert, sobald es angeklickt wurde.
CheckAlign
Liefert oder setzt einen Wert, der die horizontale und die vertikale Ausrichtung von Kontrollkästchen in einem CheckBox-Steuerelement angibt.
Checked
Gibt an, ob das CheckBox-Steuerelement mit Häkchen versehen (aktiviert) ist.
CheckState
Gibt den Zustand des Steuerelements an; eine Eigenschaft aus der CheckState-Aufzählung.
ThreeState
Gibt an, ob das CheckBox-Steuerelement drei Zustände statt nur zwei zulässt.
Ereignis
Beschreibung
AppearanceChanged
Wird ausgelöst, wenn sich die Appearance-Eigenschaft ändert.
CheckedChanged
Wird ausgelöst, wenn sich die Checked-Eigenschaft ändert.
CheckStateChanged
Wird ausgelöst, wenn sich die CheckState-Eigenschaft ändert.
Tabelle 1.6: Eigenschaften und Ereignisse des CheckBox-Steuerelements
12
Bonuskapitel 1
Das Steuerelement RadioButton Das RadioButton-Steuerelement repräsentiert ein Feld, das sich auswählen lässt. RadioButton-Steuerelemente sind normalerweise gruppiert, damit der Benutzer eine Auswahl unter mehreren treffen kann. Tabelle B.7 führt die Eigenschaften, die Methode und die Ereignisse des RadioButton-Steuerelements auf. Eigenschaft
Beschreibung
Appearance
Verwendet einen Wert aus der Appearance-Aufzählung, um die Darstellung des Steuerelements festzulegen.
AutoCheck
Gibt an, ob sich das Aussehen des Steuerelements ändert, wenn es angeklickt wird.
CheckAlign
Liefert oder setzt einen Wert, der die horizontale und die vertikale Ausrichtung von Kontrollkästchen in einem RadioButton-Steuerelement angibt.
Checked
Gibt an, ob das Steuerelement aktiviert ist.
Methode
Beschreibung
PerformClick
Erzeugt ein Click-Ereignis für das Steuerelement.
Ereignis
Beschreibung
AppearanceChanged
Wird ausgelöst, wenn sich die Appearance-Eigenschaft ändert.
CheckedChanged
Wird ausgelöst, wenn sich die Checked Eigenschaft ändert.
Tabelle 1.7: Eigenschaften, Methode und Ereignisse des RadioButton-Steuerelements
1.3
Die Steuerelemente ComboBox, ListBox und CheckedListBox
Die ListControl-Klasse stellt die übergeordnete Klasse der ComboBox-, ListBox- und CheckedListBox-Steuerelemente dar. Alle Mitglieder in ListControl werden von diesen drei Steuerelementen geerbt. Tabelle B.8 führt die Eigenschaften, die Methode und die Ereignisse von ListControl auf.
13
Bonuskapitel 1
Eigenschaft
Beschreibung
DataSource
Die Datenquelle für dieses Steuerelement.
DisplayMember
Ein Zeichenfolge, die repräsentiert, welche Eigenschaft der Datenquelle man in dem Steuerelement anzeigen möchte.
SelectedIndex
Der nullbasierte Index des aktuell ausgewählten Elements im Steuerelement.
SelectedValue
Der Wert der Mitgliedereigenschaft, der durch die ValueMember-Eigenschaft angegeben wird.
ValueMember
Ein Zeichenfolge, die die Eigenschaft der Datenquelle festlegt, aus der der Wert abgerufen wird.
Methode
Beschreibung
GetItemText
Liefert die Textdarstellung des ausgewählten Elements.
Ereignis
Beschreibung
DataSourceChanged
Wird ausgelöst, wenn sich die DataSource-Eigenschaft ändert.
DisplayMemberChanged
Wird ausgelöst, wenn sich die DisplayMember-Eigenschaft ändert.
SelectedValueChanged
Wird ausgelöst, wenn sich die SelectedValue-Eigenschaft ändert.
ValueMemberChanged Wird ausgelöst, wenn sich die ValueMember-Eigenschaft ändert.
Tabelle 1.8: Eigenschaften, Methode und Ereignisse von ListControl
Das Steuerelement ComboBox Das ComboBox-Steuerelement repräsentiert die Kombination eines ListBox-und eines TextBox-Steuerelements. Damit können Benutzer aus einer Liste von Elementen auswählen und Text eingeben oder angezeigten Text ändern. Tabelle B.9 führt die Eigenschaften, Methoden und Ereignisse des ComboBox-Steuerelements auf. Eigenschaft
Beschreibung
DrawMode
Gibt an, ob Ihr Code oder das Betriebssystem die Elemente in dem Steuerelement zeichnen soll; verwendet Werte aus der DrawMode-Aufzählung.
Tabelle 1.9: Eigenschaften, Methoden und Ereignisse von ComboBox
14
Bonuskapitel 1
Eigenschaft
Beschreibung
DropDownStyle
Das Aussehen des Steuerelements; benutzt Werte aus der ComboBoxStyle-Aufzählung.
DropDownWidth
Die Breite des Dropdownteils dieses Steuerelements.
DroppedDown
Gibt an, ob das Steuerelement seinen Dropdownteil anzeigt.
IntegralHeight
Gibt an, ob das Steuerelement seine Größe ändern soll, um so zu vermeiden, dass Elemente nur teilweise angezeigt werden.
ItemHeight
Gibt die Höhe eines Elements im Steuerelement an.
Items
Eine Auflistung von Elementen im Steuerelement.
MaxDropDownItems
Die höchste Anzahl von Elementen, die sich im Dropdownteil des Steuerelements anzeigen lassen.
MaxLength
Die höchste Anzahl von Zeichen, die in den Textfeldbereich des Steuerelements passen.
PreferredHeight
Die bevorzugte Höhe des Steuerelements.
SelectedIndex
Der nullbasierte Index des aktuell ausgewählten Elements.
SelectedItem
Das aktuell ausgewählte Element.
SelectedText
Der Text, der im Textfeldbereich des Steuerelements ausgewählt ist.
SelectionLength
Die Anzahl ausgewählter Zeichen im Textfeldbereich des Steuerelements.
SelectionStart
Der Index des am Anfang stehenden Zeichens des ausgewählten Textes im Textfeldbereich des Steuerelements.
Sorted
Gibt an, ob Elemente im Steuerelement sortiert werden sollen.
Methode
Beschreibung
BeginUpdate
Hindert das Steuerelement daran, sich selbst jedes Mal neu zu zeichnen, wenn ein neues Element hinzugefügt wurde, bis EndUpdate aufgerufen wird.
EndUpdate
Beginnt erneut mit dem Zeichnen des Steuerelements, nachdem BeginUpdate aufgerufen wurde.
FindString
Sucht das erste Element im Steuerelement, das mit der angegebenen Zeichenfolge beginnt.
Tabelle 1.9: Eigenschaften, Methoden und Ereignisse von ComboBox (Forts.)
15
Bonuskapitel 1
Methode
Beschreibung
FindStringExact
Sucht das erste Element im Steuerelement, das genau mit der angegebenen Zeichenfolge übereinstimmt.
GetItemHeight
Liefert die Höhe eines Elements im Steuerelement.
Select
Markiert einen Textbereich.
SelectAll
Markiert den vollständigen Text im Textfeldbereich des Steuerelements.
Ereignis
Beschreibung
DrawItem
Wird ausgelöst, wenn sich ein visueller Aspekt eines vom Besitzer gezeichneten Steuerelements ändert.
DropDown
Wird ausgelöst, wenn der Dropdownteil des Steuerelements gezeigt wird.
DropDownStyleChanged
Wird ausgelöst, wenn sich die DropDownStyle-Eigenschaft ändert.
MeasureItem
Tritt jedes Mal auf, wenn ein vom Besitzer gezeichnetes Steuerelement ein Element anzeigen muss.
SelectedIndexChanged
Wird ausgelöst, wenn sich die SelectedIndex-Eigenschaft ändert.
SelectionChangeCommitted
Wird ausgelöst, wenn sich das ausgewählte Element geändert hat und die Änderung gespeichert worden ist.
Tabelle 1.9: Eigenschaften, Methoden und Ereignisse von ComboBox (Forts.)
Das Steuerelement ListBox Die ListBox repräsentiert eine Liste von auswählbaren Elementen. Tabelle B.10 führt die Eigenschaften, Methoden und Ereignisse des ListBox-Steuerelements auf. Eigenschaft
Beschreibung
ColumnWidth
Die Breite der Spalten in einer mehrspaltigen ListBox.
DrawMode
Gibt an, ob Ihr Code oder das Betriebssystem die Elemente im Steuerelement zeichnet; verwendet Werte aus der DrawMode-Aufzählung.
HorizontalExtent
Die Breite, bis zu der sich ein Bildlauf mit der horizontalen Bildlaufleiste durchführen lässt.
Tabelle 1.10: Eigenschaften, Methoden und Ereignisse des ListBox-Steuerelements
16
Bonuskapitel 1
Eigenschaft
Beschreibung
HorizontalScrollbar
Gibt an, ob eine horizontale Bildlaufleiste im Steuerelement angezeigt werden soll.
IntegralHeight
Gibt an, ob das Steuerelement seine Größe ändern soll, um so zu vermeiden, dass Elemente nur teilweise angezeigt werden.
ItemHeight
Gibt die Höhe eines Elements im Steuerelement an.
Items
Ein Auflistung von Elementen im Steuerelement.
MultiColumn
Gibt an, ob das Steuerelement so viele Spalten anlegen soll wie nötig sind, um einen vertikalen Bildlauf durch die Elemente zu vermeiden.
PreferredHeight
Die bevorzugte Höhe des Steuerelements.
ScrollAlwaysVisible
Gibt an, ob die vertikale Bildlaufleiste immer angezeigt wird.
SelectedIndex
Der nullbasierte Index des aktuell ausgewählten Elements.
SelectedIndices
Eine Auflistung, die die Indizes ausgewählter Elemente im Steuerelement enthält.
SelectedItem
Das aktuell ausgewählte Element.
SelectedItems
Ein Auflistung von ausgewählten Elementen.
SelectionMode
Gibt an, wie Elemente im Steuerelement ausgewählt werden können; verwendet Werte aus der SelectionMode-Aufzählung.
Sorted
Gibt an, ob Elemente im Steuerelement sortiert werden sollen.
TopIndex
Liefert den Index des ersten Elements im Steuerelement.
UseTabStops
Gibt an, ob das Steuerelement Tabstoppzeichen in jedem Element der Liste anzeigen kann.
Methode
Beschreibung
BeginUpdate
Hindert das Steuerelement daran, sich selbst jedes Mal neu zu zeichnen, wenn ein neues Element hinzugefügt wurde, bis EndUpdate aufgerufen wird.
ClearSelected
Hebt die Auswahl aller Elemente im Steuerelement auf.
EndUpdate
Beginnt erneut mit dem Zeichnen des Steuerelements, nachdem BeginUpdate aufgerufen wurde.
Tabelle 1.10: Eigenschaften, Methoden und Ereignisse des ListBox-Steuerelements (Forts.)
17
Bonuskapitel 1
Methode
Beschreibung
FindString
Sucht das erste Element im Steuerelement, das mit der angegebenen Zeichenfolge beginnt.
FindStringExact
Sucht das erste Element im Steuerelement, das genau mit der angegebenen Zeichenfolge übereinstimmt.
GetItemHeight
Liefert die Höhe eines Elements im Steuerelement.
GetItemRectangle
Liefert das umschließende Rechteck für ein Element im Steuerelement.
GetSelected
Gibt an, ob das angegebene Element ausgewählt ist.
IndexFromPoint
Liefert den nullbasierten Index desjenigen Elements, das dem angegebenen Punkt am nächsten liegt.
SetSelected
Wählt ein angegebenes Element im Steuerelement aus oder hebt dessen Auswahl auf.
Ereignis
Beschreibung
DrawItem
Wird ausgelöst, wenn sich ein visueller Aspekt eines vom Besitzer gezeichneten Steuerelements ändert.
MeasureItem
Tritt jedes Mal auf, wenn ein vom Besitzer gezeichnetes Steuerelement ein Element anzeigen muss.
SelectedIndexChanged
Wird ausgelöst, wenn sich die SelectedIndex-Eigenschaft ändert.
Tabelle 1.10: Eigenschaften, Methoden und Ereignisse des ListBox-Steuerelements (Forts.)
Das Steuerelement CheckedListBox Das CheckedListBox-Steuerelement repräsentiert ein ListBox-Steuerelement, in dem sich neben jedem Element in der Liste Kontrollkästchen befinden. Das direkte übergeordnete Element der CheckedListBox ist das ListBox-Steuerelement, so dass es dessen gesamte Mitglieder erbt, zusätzlich zu denen im ListControl-Steuerelement. Tabelle B.11 führt Eigenschaften, Methoden und Ereignis des CheckedListBox-Steuerelements auf.
18
Bonuskapitel 1
Eigenschaft
Beschreibung
CheckedIndices
Die Indizes aller aktivierten Elemente in diesem Steuerelement.
CheckedItems
Eine Auflistung der aktivierten Elemente in diesem Steuerelement.
CheckOnClick
Gibt an, ob das Kontrollkästchen für ein Element aktiviert bzw. deaktiviert werden soll, wenn das Element angeklickt wird.
ThreeDCheckBoxes
Gibt an, ob die Kontrollkästchen mit einem 3D-Aussehen angezeigt werden sollen.
Methode
Beschreibung
GetItemChecked
Gibt an, ob das angegebene Element aktiviert ist.
GetItemCheckState
Liefert einen Wert, der den aktuellen Zustand des Kontrollkästchens für ein Element angibt.
SetItemChecked
Versieht das Element am angegebenen Index mit einem Häkchen.
SetItemCheckState
Legt den Aktivierungszustand des Elements am angegebenen Index fest.
Ereignis
Beschreibung
ItemCheck
Wird ausgelöst, wenn ein Element aktiviert wird.
Tabelle 1.11: Eigenschaften, Methoden und Ereignis von CheckedListBox
1.4
Standarddialogfeld-Steuerelemente
Standarddialogfeld-Steuerelemente repräsentieren Dialogfelder, die Standardfunktionalität des Betriebssystems zur Verfügung stellen, darunter das Öffnen und Speichern von Dateien sowie die Auswahl von Schriftarten. Jedes Standarddialogfeld ist von der System.Windows.Forms.CommonDialog-Klasse abgeleitet, deren Methoden und Ereignis in Tabelle B.12 aufgeführt sind. Methode
Beschreibung
Reset
Setzt die Eigenschaften des Steuerelements auf die Standardwerte.
ShowDialog
Zeigt das Dialogfeld an.
Ereignis
Beschreibung
HelpRequest
Wird ausgelöst, wenn der Benutzer auf die HILFE-Schaltfläche eines Standarddialogfeld-Steuerelements klickt.
Tabelle 1.12: Methoden und Ereignis von CommonDialog
19
Bonuskapitel 1
Das Steuerelement ColorDialog Das ColorDialog-Steuerelement ist ein Dialogfeld, mit dem der Benutzer aus einer Farbpalette auswählen kann; zusätzliche Steuerelemente gestatten das Definieren eigener Farben. Tabelle B.13 führt die Eigenschaften des ColorDialog-Steuerelements auf. Eigenschaft
Beschreibung
AllowFullOpen
Gibt an, ob der Benutzer das Dialogfeld nutzen kann, um eigene Farben zu definieren.
AnyColor
Gibt an, ob das Dialogfeld alle verfügbaren Farben anzeigt.
Color
Die vom Benutzer ausgewählte Farbe.
CustomColors
Liefert oder setzt die im Dialogfeld angezeigten benutzerdefinierten Farben.
FullOpen
Gibt an, ob die zur Definition benutzerdefinierter Farben verwendeten Steuerelemente angezeigt werden, während das Dialogfeld geöffnet ist.
ShowHelp
Gibt an, ob die HILFE-Schaltfläche im Dialogfeld angezeigt wird.
SolidColorOnly
Gibt an, ob das Dialogfeld Benutzern nur die Auswahl von Volltonfarben gestattet.
Tabelle 1.13: Eigenschaften von ColorDialog
Dateidialogfelder Die zwei für die Dateiverarbeitung zuständigen Standarddialogfeld-Steuerelemente (OpenFileDialog und SaveFileDialog) gleichen einander. Sie sind beide von der FileDialogKlasse abgeleitet, die in Tabelle B.14 zu sehen ist. Eigenschaft
Beschreibung
AddExtension
Gibt an, ob das Dialogfeld einem Dateinamen automatisch eine Erweiterung hinzufügen soll, falls der Benutzer dies vergisst.
CheckFileExists
Gibt an, ob das Dialogfeld prüfen soll, ob eine vom Benutzer angegebene Datei existiert.
CheckPathExists
Gibt an, ob das Dialogfeld prüfen soll, ob ein vom Benutzer angegebenes Verzeichnis existiert.
Tabelle 1.14: Eigenschaften und Ereignis von FileDialog
20
Bonuskapitel 1
Eigenschaft
Beschreibung
DefaultExt
Die Standarderweiterung für Dateinamen.
DereferenceLinks
Gibt an, ob das Dialogfeld Verknüpfungen (.lnk-Dateien) verfolgen soll oder nicht.
FileName
Liefert oder setzt eine Zeichenfolge, die den im Dialogfeld ausgewählten Dateinamen enthält.
FileNames
Die Dateinamen aller ausgewählten Dateien im Dialogfeld.
Filter
Die Filterzeichenfolge, die festlegt, welche Dateityperweiterungen der Benutzer sehen kann.
FilterIndex
Der Index des aktuell ausgewählten Filters.
InitialDirectory
Das Startverzeichnis des Dialogfeldes.
RestoreDirectory
Gibt an, ob das Dialogfeld das Originalverzeichnis nach dem Schließen wiederherstellen soll, falls der Benutzer es geändert hat.
ShowHelp
Gibt an, ob die HILFE-Schaltfläche im Dialogfeld angezeigt wird.
Title
Der Titel der Dialogfeldes.
ValidateNames
Gibt an, ob das Dialogfeld nur gültige Win32-Dateinamen akzeptiert.
Ereignis
Beschreibung
FileOk
Wird ausgelöst, wenn der Benutzer auf die ÖFFNEN- oder SPEICHERN-Schaltfläche klickt.
Tabelle 1.14: Eigenschaften und Ereignis von FileDialog (Forts.)
OpenFileDialog OpenFileDialog ermöglicht es dem Benutzer, eine zu öffnende Datei auszuwählen. Tabelle B.15 führt die Eigenschaften und die Methode des OpenFileDialog-Steuerelements auf. Eigenschaft
Beschreibung
MultiSelect
Gibt an, ob der Benutzer mehrere Dateien auswählen kann.
ReadOnlyChecked
Gibt an, ob das Kontrollkästchen SCHREIBGESCHÜTZT aktiviert ist.
ShowReadOnly
Gibt an, ob das Kontrollkästchen SCHREIBGESCHÜTZT angezeigt werden soll.
Tabelle 1.15: Eigenschaften und Methode von OpenFileDialog
21
Bonuskapitel 1
Methode
Beschreibung
OpenFile
Öffnet die vom Benutzer ausgewählte Datei schreibgeschützt.
Tabelle 1.15: Eigenschaften und Methode von OpenFileDialog (Forts.)
SaveFileDialog SaveFileDialog gestattet dem Benutzer, eine Datei zu speichern, wobei er eine vorhandene
Datei überschreiben oder einen neuen Dateinamen benutzen kann. Tabelle B.16 führt die Eigenschaften und die Methode des SaveFileDialog-Steuerelements auf. Eigenschaft
Beschreibung
CreatePrompt
Gibt an, ob das Dialogfeld den Benutzer fragen soll, ob eine Datei anzulegen ist, falls es sie noch nicht gibt.
OverwritePrompt
Gibt an, ob das Dialogfeld den Benutzer fragen soll, ob eine Datei zu überschreiben ist, falls es sie bereits gibt.
Methode
Beschreibung
OpenFile
Öffnet die vom Benutzer ausgewählte Datei schreibgeschützt.
Tabelle 1.16: Eigenschaften und Methode von SaveFileDialog
FontDialog FontDialog ermöglicht es dem Benutzer, eine Schriftart und deren Eigenschaften (Größe, Auszeichnung usw.) für den Einsatz in einer Anwendung auszuwählen. Tabelle B.17 führt Eigenschaften und Ereignis des FontDialog-Steuerelements auf. Eigenschaft
Beschreibung
AllowScriptChange
Gibt an, ob der Benutzer die Standardschriftart ändern kann, um einen anderen Zeichensatz anzuzeigen.
AllowSimulations
Gibt an, ob das Dialogfeld GDI-Schriftartsimulationen zulässt.
AllowVectorFonts
Gibt an, ob das Dialogfeld die Auswahl von Vektorschriften zulässt.
AllowVerticalFonts Gibt an, ob das Dialogfeld vertikale Schriftarten zulässt. Color
Gibt die Schriftfarbe an.
Tabelle 1.17: Eigenschaften und Ereignis von FontDialog
22
Bonuskapitel 1
Eigenschaft
Beschreibung
FixedPitchOnly
Gibt an, ob das Dialogfeld nur Schriftarten mit fester Zeichenbreite zulässt.
Font
Die ausgewählte Schriftart.
FontMustExist
Gibt an, ob das Dialogfeld eine Fehlermeldung auslöst, wenn die ausgewählte Schriftart nicht existiert.
MaxSize
Die höchste Schriftgröße, die ein Benutzer auswählen kann.
MinSize
Die kleinste Schriftgröße, die ein Benutzer auswählen kann.
ScriptsOnly
Gibt an, ob das Dialogfeld auch Nicht-OEM- und Symbolzeichensätze zulässt.
ShowApply
Gibt an, ob das Dialogfeld über eine ÜBERNEHMEN-Schaltfläche verfügt.
ShowColor
Gibt an, ob das Dialogfeld dem Benutzer gestattet, eine Schriftfarbe zu wählen.
ShowEffects
Gibt an, ob das Dialogfeld dem Benutzer gestattet, Effekte wie etwa Durchstreichen, Unterstreichen und Farbe zu wählen.
ShowHelp
Gibt an, ob die HILFE-Schaltfläche im Dialogfeld angezeigt wird.
Ereignis
Beschreibung
Apply
Wird ausgelöst, wenn die ÜBERNEHMEN-Schaltfläche angeklickt wird.
Tabelle 1.17: Eigenschaften und Ereignis von FontDialog (Forts.)
PageSetupDialog Mit PageSetupDialog kann der Benutzer seitenrelevante Eigenschaften wie etwa Ränder und Papierquelle festlegen. Tabelle B.18 führt die Eigenschaften des PageSetupDialogSteuerelements auf. Eigenschaft
Beschreibung
AllowMargins
Gibt an, ob das Dialogfeld dem Benutzer gestattet, Werte für die Ränder zu ändern.
AllowOrientation
Gibt an, ob das Dialogfeld dem Benutzer gestattet, die Papierausrichtung (Quer- oder Hochformat) zu wählen.
Tabelle 1.18: Eigenschaften von PageSetupDialog
23
Bonuskapitel 1
Eigenschaft
Beschreibung
AllowPaper
Gibt an, ob das Dialogfeld dem Benutzer gestattet, Papiergröße und -quelle zu wählen.
AllowPrinter
Gibt an, ob das Dialogfeld die Druckerschaltfläche zeigen soll.
Document
Gibt das PrintDocument-Objekt an, von dem Seiteneinstellungen geholt werden sollen.
MinMargins
Die kleinsten Randabstände, die ein Benutzer angeben darf, ausgedrückt in 1/ 100 Zoll.
PageSettings
Das zu ändernde PageSettings-Objekt.
PrinterSettings
Die zu verwendenden Druckereinstellungen, wenn der Benutzer die Druckerschaltfläche anklickt.
ShowHelp
Gibt an, ob die HILFE-Schaltfläche im Dialogfeld angezeigt wird.
ShowNetwork
Gibt an, ob das Dialogfeld die Netzwerkschaltfläche anzeigen soll.
Tabelle 1.18: Eigenschaften von PageSetupDialog (Forts.)
PrintDialog Mit PrintDialog kann der Benutzer ein Dokument drucken. Tabelle B.19 führt die Eigenschaften des PrintDialog-Steuerelements auf. Eigenschaft
Beschreibung
AllowPrintToFile
Gibt an, ob das Kontrollkästchen AUSGABE IN DATEI UMLEITEN ANgezeigt wird.
AllowSelection
Gibt an, ob die VON…BIS…SEITE-Option aktiviert ist.
AllowSomePages
Gibt an, ob die SEITEN-Option aktiviert ist.
Document
Gibt das PrintDocument-Objekt an, von dem Seiteneinstellungen geholt werden sollen.
PrinterSettings
Die Druckereinstellungen, von denen Einstellungen abzurufen sind.
PrintToFile
Gibt an, ob das Kontrollkästchen AUSGABE IN DATEI UMLEITEN aktiviert ist.
ShowHelp
Gibt an, ob die HILFE-Schaltfläche im Dialogfeld angezeigt wird.
ShowNetwork
Gibt an, ob das Dialogfeld die Netzwerkschaltfläche anzeigen soll.
Tabelle 1.19: Eigenschaften von PrintDialog
24
Bonuskapitel 1
1.5
Menüs
Menüs repräsentieren Gruppen von Befehlen, die sich ausführen lassen, nachdem eine speisekartenähnliche Liste angezeigt wurde. Dazu gehören reguläre Anwendungsmenüs und kontextabhängige Menüs. Alle Steuerelemente in diesem Abschnitt sind von der System.Windows.Forms.Menu-Klasse abgeleitet, die in Tabelle B.20 gezeigt wird. Eigenschaft
Beschreibung
Handle
Der Handle für ein vorliegendes Menü.
IsParent
Gibt an, ob dieses Menü über Untermenüs verfügt.
MdiListItem
Gibt an, ob dieses Menü dazu benutzt werden soll, die Fenster einer MDIAnwendung anzuzeigen.
MenuItems
Eine Auflistung der in diesem Menü enthaltenen Untermenüs.
Methode
Beschreibung
GetContextMenu
Liefert das ContextMenu, zu dem dieses Menu gehört.
GetMainMenu
Liefert das MainMenu-Steuerelement, zu dem dieses Menu gehört.
MergeMenu
Führt die Menüelemente eines angegebenen Menüs mit dem aktuellen Menü zusammen.
Tabelle 1.20: Eigenschaften und Methoden von Menu
ContextMenu Ein Kontextmenü bildet ein Menü, das kontextsensitiv ist. Dieses Menü erscheint normalerweise, wenn der Benutzer auf die rechte Maustaste klickt, sobald sich die Maus über einem Objekt befindet. Tabelle B.21 führt die Methoden und das Ereignis des ContextMenu-Steuerelements auf. Methode
Beschreibung
Show
Zeigt das Menü an der angegebenen Position an.
SourceControl
Liefert das Steuerelement, das das Kontextmenü enthält.
Tabelle 1.21: Methode und Ereignis von ContextMenu
25
Bonuskapitel 1
Ereignis
Beschreibung
Popup
Tritt direkt vor der Anzeige eines Kontextmenüs auf.
Tabelle 1.21: Methode und Ereignis von ContextMenu(Forts.)
MainMenu Das MainMenu-Steuerelement repräsentiert die oberste Menüstruktur eines Formulars. Es zeigt nicht selbst ein Menü an, enthält aber MenuItem-Steuerelemente, die dies tun. Tabelle B.22 führt die Methoden des MainMenu-Steuerelements auf. Methode
Beschreibung
CloneMenu
Erzeugt eine Kopie des MainMenu-Objekts.
GetForm
Liefert das Form-Objekt für dieses Menüelement.
Tabelle 1.22: Methoden von MainMenu
MenuItem Ein MenuItem repräsentiert die eigentliche Benutzerschnittstelle für ein Menü; es ist der sichtbare Anteil des Menüs, mit dem der Benutzer interagieren kann. Tabelle B.23 führt Eigenschaften, Methoden und Ereignisse des MenuItem-Steuerelements auf. Eigenschaft
Beschreibung
BarBreak
Gibt an, ob ein Menüelement in einer neuen Zeile angezeigt werden soll (in einem umgebenden MenuItem-Objekt) oder in einer neuen Spalte (in einem ContextMenu-Objekt).
Break
Gibt an, ob ein Element in einer neuen Zeile angezeigt werden soll (in einem umgebenden MenuItem-Objekt) oder in einer neuen Spalte (in einem ContextMenu-Objekt).
Checked
Gibt an, ob ein Häkchen neben dem Menüelement erscheint.
DefaultItem
Gibt an, ob dieses Menüelement das Standardmenüelement ist.
Enabled
Gibt an, ob das Menüelement Benutzerinteraktion zulässt.
Tabelle 1.23: Eigenschaften, Methoden und Ereignisse von MenuItem
26
Bonuskapitel 1
Eigenschaft
Beschreibung
Index
Die Position dieses Menüelements in seinem übergeordneten Menü.
MdiList
Gibt an, ob dieses Menüelement dazu benutzt werden soll, die Fenster einer MDI-Anwendung anzuzeigen.
MergeOrder
Ein Wert, der die Position dieses Menüelements in Bezug auf andere angibt, wenn es mit einem anderen Menü zusammengeführt wird.
MergeType
Gibt die Verhaltensweise dieses Menüelements in Bezug auf andere an, wenn es mit einem anderen Menü zusammengeführt wird; verwendet Werte aus der MenuMerge-Aufzählung.
Mnemonic
Das mnemonische Zeichen, das mit diesem Menü verknüpft ist.
OwnerDraw
Gibt an, ob der Besitzer oder das Betriebssystem das Menüelement zeichnet.
Parent
Liefert das übergeordnete Menü des aktuellen Menüelements.
RadioCheck
Gibt an, ob das Menüelement ein Optionsfeld statt eines Häkchens anzeigt.
Shortcut
Die diesem Menüelement zugeordnete Tastenkombination; verwendet Werte aus der ShortCut-Aufzählung.
ShowShortcut
Gibt an, ob das Menüelement die zugeordnete Tastenkombination neben der Menübeschriftung anzeigen soll.
Text
Die Aufschrift, die das Menüelement trägt.
Visible
Gibt an, ob das Menüelement für den Benutzer sichtbar ist.
Methode
Beschreibung
CloneMenu
Erzeugt eine Kopie des Menüelements.
PerformClick
Löst für das Menüelement ein Click-Ereignis aus.
PerformSelect
Löst für das Menüelement ein Select-Ereignis aus.
Ereignis
Beschreibung
DrawItem
Wird ausgelöst, wenn ein Menüelement gezeichnet werden muss und die OwnerDraw-Eigenschaft auf true gesetzt ist.
MeasureItem
Wird ausgelöst, wenn ein Menüelement auszumessen ist, bevor es gezeichnet wird und die OwnerDraw-Eigenschaft auf true gesetzt ist.
Tabelle 1.23: Eigenschaften, Methoden und Ereignisse von MenuItem (Forts.)
27
Bonuskapitel 1
Eigenschaft
Beschreibung
Popup
Wird ausgelöst, bevor ein Menüelement gezeigt wird.
Select
Wird ausgelöst, wenn der Benutzer den Mauspzeiger über einem Menüelement platziert.
Tabelle 1.23: Eigenschaften, Methoden und Ereignisse von MenuItem (Forts.)
1.6
Das Steuerelement DataGrid
Dieses Steuerelement repräsentiert eine editierbare Tabelle von Daten. Tabelle B.24 führt die Eigenschaften, Methoden und Ereignisse des DataGrid-Steuerelements auf. Eigenschaft
Beschreibung
AllowNavigation
Gibt an, ob die Navigation zu untergeordneten Tabellen und Datasets zulässig ist.
AllowSorting
Gibt an, ob der Benutzer die Daten neu sortieren kann, indem er auf die Kopfzeile einer Spalte klickt.
AlternatingBackColor
Die Hintergrundfarbe für abwechselnde Zeilen.
BackColor
Die Hintergrundfarbe des Datenblattes.
BackgroundColor
Die Hintergrundfarbe der Bereiche des Datenblattes, die außerhalb der Zeilen liegen.
BorderStyle
Der Randstil des Datenblattes; verwendet einen Wert der BorderStyle-Aufzählung.
CaptionBackColor
Die Hintergrundfarbe des Beschriftungsbereichs.
CaptionFont
Die im Beschriftungsbereich verwendete Schriftart.
CaptionForeColor
Die Vordergrundfarbe des Beschriftungsbereichs.
CaptionText
Der im Beschriftungsbereich angezeigte Text.
CaptionVisible
Gibt an, ob der Beschriftungsbereich sichtbar ist.
ColumnHeadersVisible
Gibt an, ob die Kopfzeilen jeder Spalte sichtbar sind.
CurrentCell
Liefert oder setzt die Zelle, die den Fokus hat.
Tabelle 1.24: Eigenschaften, Methoden und Ereignisse von DataGrid
28
Bonuskapitel 1
Eigenschaft
Beschreibung
CurrentRowIndex
Der Index der ausgewählten Zeile.
DataMember
Liefert die spezifische Liste in der DataSource-Eigenschaft, die im Datenblatt angezeigt werden soll.
DataSource
Die Datenquelle, die das Datenblatt anzeigen soll.
FirstVisibleColumn
Der Index der ersten sichtbaren Spalte im Datenblatt.
FlatMode
Gibt an, ob das Datenblatt mit 3D-Effekten oder flach angezeigt werden soll.
GridLineColor
Die Farbe der Datenblattlinien.
GridLineStyle
Der Stil der Datenblattlinien; verwendet Werte aus der DataGridLineStyle-Aufzählung.
HeaderBackColor
Die Hintergrundfarbe aller Kopfzellen von Zeilen und Spalten.
HeaderFont
Die Schriftart aller Zeilen- und Spaltenköpfe.
HeaderForeColor
Die Vordergrundfarbe aller Zeilen- und Spaltenköpfe.
Item
Der Wert einer angegebenen Zelle.
LinkColor
Die Farbe des Textes, den man anklicken kann, um zu untergeordneten Tabellen zu navigieren.
LinkHoverColor
Die Farbe, die ein Link annimmt, wenn der Mauszeiger darüber platziert wird.
ParentRowsBackColor
Die Hintergrundfarbe von übergeordneten Zeilen.
ParentRowsForeColor
Die Vordergrundfarbe von übergeordneten Zeilen
ParentRowsLabelStyle
Die Art und Weise, wie übergeordnete Zeilen angezeigt werden; verwendet Werte aus der Aufzählung DataGridParentRowsLabelStyle.
ParentRowsVisible
Gibt an, ob Zeilen der übergeordneten Tabelle sichtbar sein sollen.
PreferredColumnWidth
Die Standardbreite von Spalten, ausgedrückt in Bildpunkten.
PreferredRowHeight
Die Standardhöhe jeder Zeile.
ReadOnly
Gibt an, ob sich die Daten im Datenblatt editieren lassen.
RowHeadersVisible
Gibt an, ob die Zeilenköpfe sichtbar sind.
Tabelle 1.24: Eigenschaften, Methoden und Ereignisse von DataGrid (Forts.)
29
Bonuskapitel 1
Eigenschaft
Beschreibung
RowHeaderWidth
Die Breite der Zeilenköpfe.
SelectionBackColor
Die Hintergrundfarbe von ausgewählten Zeilen.
SelectionForeColor
Die Vordergrundfarbe von ausgewählten Zeilen.
TableStyles
Ein Auflistung von DataGridTableStyle-Objekten, die das Layout für das Datenblatt definieren.
VisibleColumnCount
Die Anzahl sichtbarer Spalten.
VisibleRowCount
Die Anzahl sichtbarer Zeilen.
Methode
Beschreibung
BeginEdit
Versucht, das Datenblatt in einen Zustand zu versetzen, in dem die Bearbeitung erlaubt ist.
BeginInit
Beginnt die Initialisierung des Datenblattes.
Collapse
Reduziert die Anzeige untergeordneter Beziehungen (das Gegenteil von Expand).
EndEdit
Beendet den Bearbeitungsmodus, der von BeginEdit aufgerufen wurde.
EndInit
Beendet die Initialisierung des Datenblattes.
Expand
Zeigt die untergeordneten Beziehungen für jede einzelne oder alle Zeilen an.
GetCellBounds
Ein Rectangle-Objekt, das die vier Ecken einer beliebigen Zelle festlegt.
GetCurrentCellBounds
Ein Rectangle-Objekt, das die vier Ecken einer ausgewählten Zelle festlegt.
HitTest
Liefert Informationen über das Datenblatt am angegebenen Punkt.
IsExpanded
Gibt an, ob der Knoten einer Zeile gerade erweitert oder reduziert ist.
IsSelected
Gibt an, ob eine Zeile ausgewählt ist.
NavigateBack
Navigiert zu der Tabelle zurück, die zuvor im Datenblatt angezeigt worden war.
NavigateTo
Navigiert zur angegebenen Tabelle.
ResetAlternatingBackColor
Setzt die AlternatingBackColor-Eigenschaft auf deren Standardwert.
Tabelle 1.24: Eigenschaften, Methoden und Ereignisse von DataGrid (Forts.)
30
Bonuskapitel 1
Methode
Beschreibung
ResetGridLineColor
Setzt die GridLineColor-Eigenschaft auf deren Standardwert.
ResetHeaderBackColor
Setzt die HeaderBackColor-Eigenschaft auf deren Standardwert.
ResetHeaderFont
Setzt die HeaderFont-Eigenschaft auf deren Standardwert.
ResetHeaderForeColor
Setzt die HeaderForeColor-Eigenschaft auf deren Standardwert.
ResetLinkColor
Setzt die LinkColor-Eigenschaft auf deren Standardwert.
ResetSelectionBackColor
Setzt die SelectionBackColor-Eigenschaft auf deren Standardwert.
ResetSelectionForeColor
Setzt die SelectionForeColor-Eigenschaft auf deren Standardwert.
Select
Wählt eine angegebene Zeile aus.
SetDataBinding
Setzt die DataSource- und DataMember-Eigenschaften.
UnSelect
Beendet die Auswahl einer bestimmten Zeile.
Ereignis
Beschreibung
AllowNavigationChanged
Wird ausgelöst, wenn sich die AllowNavigation-Eigenschaft ändert.
BackButtonClick
Wird ausgelöst, wenn die ZURÜCK-Schaltfläche in einer untergeordneten Tabelle angeklickt wird.
BackgroundColorChanged
Wird ausgelöst, wenn sich die BackgroundColor-Eigenschaft ändert.
BorderStyleChanged
Wird ausgelöst, wenn sich die BorderStyle-Eigenschaft ändert.
CaptionVisibleChanged
Wird ausgelöst, wenn sich die CaptionVisible-Eigenschaft ändert.
CurrentCellChanged
Wird ausgelöst, wenn sich die CurrentCell-Eigenschaft ändert.
DataSourceChanged
Wird ausgelöst, wenn sich die DataSource-Eigenschaft ändert.
FlatModeChanged
Wird ausgelöst, wenn sich die FlatMode-Eigenschaft ändert.
Navigate
Wird ausgelöst, wenn der Benutzer zu einer anderen Tabelle im Datenblatt navigiert.
ParentRowsLabelStyleChanged
Wird ausgelöst, wenn sich die ParentRowsLabelStyle-Eigenschaft ändert.
ParentRowsVisibleChanged
Wird ausgelöst, wenn sich die ParentRowsVisible-Eigenschaft ändert.
Tabelle 1.24: Eigenschaften, Methoden und Ereignisse von DataGrid (Forts.)
31
Bonuskapitel 1
Ereignis
Beschreibung
ReadOnlyChanged
Wird ausgelöst, wenn sich die ReadOnly-Eigenschaft ändert.
Scroll
Wird ausgelöst, wenn der Benutzer einen Bildlauf durch das Datenblatt ausführt.
ShowParentDetailsButtonClick
Wird ausgelöst, wenn die Schaltfläche zum Anzeigen übergeordneter Details angeklickt wird.
Tabelle 1.24: Eigenschaften, Methoden und Ereignisse von DataGrid (Forts.)
1.7
DateTimePicker
Das DateTimePicker-Steuerelement repräsentiert ein Kalender- und Zeit-Steuerelement, das sich dazu verwenden lässt, Datums- und Zeitinformationen einzustellen oder auszuwählen. Tabelle B.25 beschreibt die Eigenschaften und Ereignisse des DateTimePickerSteuerelements. Eigenschaft
Beschreibung
CalendarFont
Die im Kalender verwendete Schriftart.
CalendarForeColor
Die Vordergrundfarbe des Kalenders.
CalendarMonthBackground
Die Hintergrundfarbe des Kalendermonats.
CalendarTitleBackColor
Die Hintergrundfarbe des Titels des Kalenders.
CalendarTitleForeColor
Die Vordergrundfarbe des Titels des Kalenders.
CalendarTrailingForeColor
Die Vordergrundfarbe der Folgedaten des Kalenders.
Checked
Gibt an, ob die Value-Eigenschaft auf ein gültiges Datum eingestellt und der angezeigte Wert aktualisierbar ist.
CustomFormat
Die benutzerdefinierte Formatzeichenfolge für Datum und Uhrzeit.
DropDownAlign
Die Ausrichtung des Dropdownkalenders im Steuerelement; verwendet einen Wert aus der LeftRightAlignment-Aufzählung.
Format
Das Format des angezeigten Datums und der Zeit.
Tabelle 1.25: Eigenschaften und Ereignisse von DateTimePicker
32
Bonuskapitel 1
Eigenschaft
Beschreibung
MaxDate
Die höchste Datums- und Zeitangabe, die in diesem Steuerelement ausgewählt werden kann.
MinDate
Die kleinste Datums- und Zeitangabe, die in diesem Steuerelement ausgewählt werden kann.
PreferredHeight
Die bevorzugte Höhe des Steuerelements.
ShowCheckBox
Gibt an, ob ein Kontrollkästchen links des Datums angezeigt wird.
ShowUpDown
Gibt an, ob ein Auf-Ab-Steuerelement benutzt wird, um Datum und Zeit anzupassen.
value
Der Datums- und Zeitwert des Steuerelements.
Ereignis
Beschreibung
CloseUp
Wird ausgelöst, wenn der Dropdownkalender geschlossen wird.
DropDown
Wird ausgelöst, wenn der Dropdownkalender angezeigt wird.
FormatChanged
Wird ausgelöst, wenn sich die Format-Eigenschaft ändert.
ValueChanged
Wird ausgelöst, wenn sich die Value-Eigenschaft ändert.
Tabelle 1.25: Eigenschaften und Ereignisse von DateTimePicker (Forts.)
1.8
Bildlauffähige Steuerelemente
Bildlauffähige Steuerelemente (Scrollable Controls) sind ein spezieller Steuerelementtyp, der den automatischen Bildlauf unterstützt, wenn die Inhalte des Steuerelements zu groß sind, um auf einen Bildschirm zu passen. Als Beispiele seien die Form- und Panel-Steuerelemente genannt. Alle bildlauffähigen Steuerelemente erben Werte von der System.Windows.Forms.ScrollableControl-Klasse, die in Tabelle B.26 gezeigt wird. Eigenschaft
Beschreibung
AutoScroll
Gibt an, ob das Steuerelement dem Benutzer gestattet, einen Bildlauf zu untergeordneten Elementen auszuführen, die außerhalb der Grenzen des Containers liegen.
AutoScrollMargin
Die Größe des Autobildlaufrandes.
Tabelle 1.26: Eigenschaften und Methode von ScrollableControl
33
Bonuskapitel 1
Eigenschaft
Beschreibung
AutoScrollMinSize
Die kleinste Größe des Autobildlaufs.
AutoScrollPosition
Die Position des Autobildlaufs.
DockPadding
Der Seitenabstand für alle Ränder des Containersteuerelements.
Methode
Beschreibung
SetAutoScrollMargin
Legt die Größe der Autobildlaufränder fest.
Tabelle 1.26: Eigenschaften und Methode von ScrollableControl (Forts.)
Darüber hinaus haben die Steuerelemente Form, PropertyGrid, PrintPreviewDialog, DomainUpDown und NumericUpDown Eigenschaften aus der System.Windows.Forms.ContainerControl-Klasse gemeinsam, die in Tabelle B.27 gezeigt wird. ContainerControl erbt von ScrollableControl. Eigenschaft
Beschreibung
ActiveControl
Das aktive Steuerelement im Container.
ParentForm
Der übergeordnete Container des Steuerelements.
Methode
Beschreibung
Validate
Prüft die Gültigkeit des zuletzt für ungültig erklärten Steuerelements und dessen übergeordneter Steuerelemente, jedoch ohne das aktuelle Steuerelement einzubeziehen.
Tabelle 1.27: Eigenschaften und Methode von ContainerControl
Zusätzlich zu den ScrollableControl- und ContainerControl-Klassen erben die Steuerelemente DomainUpDown und NumericUpDown auch vom System.Windows.Forms.UpDownBaseSteuerelement, das in Tabelle B.28 gezeigt wird. Eigenschaft
Beschreibung
BorderStyle
Der Darstellungsstil des Rahmens um dieses Steuerelement; verwendet Werte aus der BorderStyle-Aufzählung.
InterceptArrowKeys
Gibt an, ob der Benutzer die Auf- und Abwärtspfeiltasten benutzen kann, um Werte auszuwählen.
Tabelle 1.28: Eigenschaften und Methoden von UpDownBase
34
Bonuskapitel 1
Eigenschaft
Beschreibung
PreferredHeight
Die bevorzugte Höhe des Steuerelements.
ReadOnly
Gibt an, ob sich der Wert des Steuerelements ändern lässt.
TextAlign
Die Ausrichtung des Textes im Steuerelement; verwendet Werte aus der HorizontalAlignment-Aufzählung.
UpDownAlign
Die Ausrichtung der Auf- und Abwärts-Schaltflächen auf dem Steuerelement; verwendet Werte aus der LeftRightAlignment-Aufzählung.
Methode
Beschreibung
DownButton
Dient der Verarbeitung des Ereignisses, wenn die Abwärts-Schaltfläche gedrückt wird.
Select
Markiert einen Textbereich im Steuerelement.
UpButton
Dient der Verarbeitung des Ereignisses, wenn die Aufwärts-Schaltfläche gedrückt wird.
Tabelle 1.28: Eigenschaften und Methoden von UpDownBase (Forts.)
DomainUpDown Dieses Steuerelement repräsentiert ein bildlauffähiges Steuerelement, das Zeichenfolgen anzeigt. Diese Klasse erbt direkt von der UpDownBase-Klasse und indirekt von ContainerControl und ScrollableControl. Tabelle B.29 zeigt die Eigenschaften dieses Steuerelements. Eigenschaft
Beschreibung
Items
Eine Auflistung von Objekten, die diesem Steuerelement zugewiesen sind.
SelectedIndex
Der nullbasierte Index des aktuell ausgewählten Elements im Steuerelement.
SelectedItem
Das aktuell ausgewählte Element im Steuerelement.
Sorted
Gibt an, ob die Elemente im Steuerelement sortiert sind.
Wrap
Gibt an, ob sich das Steuerelement zum ersten Element bewegt, wenn der Benutzer das Listenende überschreitet, oder zum letzten Element, wenn der Benutzer den Listenanfang überschreitet.
Tabelle 1.29: Eigenschaften von DomainUpDown
35
Bonuskapitel 1
Form Dieses Containersteuerelement repräsentiert ein Fenster in einer Anwendung (normalerweise den Hauptcontainer in einer Anwendung). Diese Klasse erbt direkt von ContainerControl und indirekt von ScrollableControl. Tabelle B.30 führt die Eigenschaften, Methoden und Ereignisse dieses Steuerelements auf. Eigenschaft
Beschreibung
AcceptButton
Die Schaltfläche, die mit der (¢)-Taste verknüpft ist.
ActiveForm
Liefert das aktuell aktive Formular dieser Anwendung.
ActiveMdiChild
Das aktuell aktive untergeordnete MDI-Fenster.
AutoScale
Gibt an, ob das Formular automatisch seine Größe anpasst, um sich verschiedenen Schriftgrößen anzupassen.
AutoScaleBaseSize
Die für die Autoskalierung verwendete Basisgröße.
CancelButton
Die Schaltfläche, die mit der (Esc)-Taste verknüpft ist.
ClientSize
Die Größe des Clientbereichs des Formulars.
ControlBox
Gibt an, ob der Rahmen des Steuerelements im Formular angezeigt wird.
DesktopBounds
Größe und Position des Formulars auf dem Clientdesktop.
DesktopLocation
Die Position des Formulars auf dem Clientdesktop.
DialogResult
Der Wert, den das Formular liefert, wenn es geschlossen ist; verwendet einen Wert aus der DialogResult-Aufzählung.
FormBorderStyle
Gibt den Darstellungsstil des Rahmens des Formulars an; verwendet einen Wert aus der FormBorderStyle-Aufzählung.
HelpButton
Gibt an, ob eine HILFE-Schaltfläche im Formular angezeigt werden soll.
Icon
Das Symbol, das mit dem Formular verknüpft ist; wenn es das oberste Formular ist, wird dieses Symbol benutzt, um Ihre Anwendung zu repräsentieren.
IsMdiChild
Gibt an, ob das Formular ein untergeordnetes MDI-Formular ist.
IsMdiContainer
Gibt an, ob das Formular ein Container für untergeordnete MDI-Formulare ist.
Tabelle 1.30: Eigenschaften, Methoden und Ereignisse von Form
36
Bonuskapitel 1
Eigenschaft
Beschreibung
KeyPreview
Gibt an, ob das Formular vor dem Steuerelement, das den Fokus hat, Tastaturereignisse empfängt.
MaximizeBox
Gibt an, ob die Schaltfläche für Maximieren zu sehen ist.
MaximumSize
Gibt die maximale Größe an, zu der der Benutzer das Formular skalieren kann.
MdiChildren
Liefert ein Array von Formularen, die die untergeordneten MDI-Formulare dieses Formulars darstellen.
MdiParent
Liefert oder setzt das übergeordnete MDI-Formular dieses Formulars.
Menu
Das MainMenu, das mit diesem Formular verknüpft ist.
MergedMenu
Das zusammengeführte Menü des Formulars.
MinimizeBox
Gibt an, ob die Schaltfläche für Minimieren im Formular angezeigt wird.
MinimumSize
Die minimale Größe, auf die sich das Formular skalieren lässt.
Modal
Gibt die Modalität des Formulars an (d.h. ob es dem Benutzer erlaubt, von diesem Formular zu einem anderen zu wechseln).
Opacity
Die Durchlässigkeit des Formulars.
OwnedForms
Ein Array von Form-Objekten, die alle Formulare repräsentieren, deren Besitzer das aktuelle Formular ist.
Owner
Liefert oder setzt das Formular, das der Besitzer des aktuellen Formulars ist.
ShowInTaskbar
Gibt an, ob das Formular in der Windows-Taskleiste zu sehen ist.
SizeGripStyle
Bestimmt den Typ von Größenziehpunkten, die der Benutzer zur Skalierung des Formulars benutzen kann; verwendet Werte aus der SizeGripStyle-Aufzählung.
StartPosition
Die Ausgangsposition des Formulars.
TopLevel
Gibt an, ob das Formular als oberstes Fenster anzuzeigen ist.
TopMost
Gibt an, ob das Formular als oberstes in einer Anwendung anzuzeigen ist.
TransparencyKey
Die Farbe, die transparente Flächen des Formulars darstellt.
WindowState
Der Zustand des Formulars; verwendet Werte aus der FormWindowState-Aufzählung.
Tabelle 1.30: Eigenschaften, Methoden und Ereignisse von Form (Forts.)
37
Bonuskapitel 1
Methode
Beschreibung
Activate
Aktiviert das Formular und übergibt ihm den Fokus.
AddOwnedForm
Fügt dem aktuellen Formular ein Formular hinzu.
Close
Schließt das Formular.
LayoutMdi
Arrangiert die untergeordneten MDI-Formulare im übergeordneten Formular; verwendet Werte der MdiLayout-Aufzählung.
RemovedOwnedForm
Entfernt ein Formular, das einem Besitzer gehört, aus dem aktuellen Formular.
SetDesktopBounds
Setzt die Grenzen des Formulars auf dem Clientdesktop.
SetDesktopLocation
Setzt die Position des Formulars auf dem Clientdesktop.
ShowDialog
Zeigt das Formular als modales Dialogfeld an.
Ereignis
Beschreibung
Activated
Wird ausgelöst, wenn das Formular aktiviert wird.
Closed
Wird ausgelöst, wenn das Formular geschlossen wird.
Closing
Wird direkt, bevor das Formular geschlossen wird, ausgelöst.
Deactivate
Wird ausgelöst, wenn das Formular den Fokus verliert.
InputLanguageChanged
Wird ausgelöst, wenn sich die Eingabesprache des Formulars ändert.
InputLanguageChanging
Wird direkt, bevor sich die Eingabesprache des Formulars ändert, ausgelöst.
Load
Wird ausgelöst, wenn das Formular zum ersten Mal angezeigt wird.
MaximizedBoundsChanged
Wird ausgelöst, wenn sich die MaximizedBounds-Eigenschaft ändert.
MaximumSizeChanged
Wird ausgelöst, wenn sich die MaximumSize-Eigenschaft ändert.
MdiChildActivate
Wird ausgelöst, wenn ein untergeordnetes MDI-Formular aktiviert oder geschlossen wird.
MenuComplete
Wird ausgelöst, wenn das Menü des Formulars den Fokus verliert.
MenuStart
Wird ausgelöst, wenn das Menü des Formulars den Fokus erhält.
MinimumSizeChanged
Wird ausgelöst, wenn sich die MinimumSize-Eigenschaft ändert.
Tabelle 1.30: Eigenschaften, Methoden und Ereignisse von Form (Forts.)
38
Bonuskapitel 1
NumericUpDown NumericUpDown repräsentiert ein bildlauffähiges Windows-Steuerelement, das numerische Werte anzeigt. Diese Klasse erbt direkt von der UpDownBase-Klasse und indirekt von ContainerControl und ScrollableControl. Tabelle B.31 führt Eigenschaften und Ereignis dieses Steuerelements auf. Eigenschaft
Beschreibung
DecimalPlaces
Die Anzahl der Dezimalstellen, die im Steuerelement anzuzeigen sind.
Hexadecimal
Gibt an, ob das Steuerelement hexadezimale Werte anzeigen soll.
Increment
Der Wert, um den der Wert des Steuerelements zu erhöhen oder zu verringern ist, wenn die Auf- oder Ab-Schaltflächen angeklickt werden.
Maximum
Der höchste Wert für das Steuerelement.
Minimum
Der kleinste Wert für das Steuerelement.
ThousandsSeparator
Gibt an, ob Tausendertrennzeichen anzuzeigen sind.
Value
Der aktuell im Steuerelement angezeigte Wert.
Ereignis
Beschreibung
ValueChanged
Wird ausgelöst, wenn sich die value-Eigenschaft ändert.
Tabelle 1.31: Eigenschaften und Ereignis von NumericUpDown
Panel Dieses Steuerelement repräsentiert eine Grundfläche, die andere Steuerelemente enthalten kann, selbst aber ebenfalls in einem anderen Steuerelement enthalten sein muss. Diese Klasse erbt direkt von ScrollableControl. Tabelle B.32 führt die einzige Eigenschaft dieses Steuerelements auf. Eigenschaft
Beschreibung
BorderStyle
Der Rahmentyp um das Steuerelement herum; verwendet Werte aus der BorderStyle-Aufzählung.
Tabelle 1.32: Eigenschaft von Panel
39
Bonuskapitel 1
PrintPreviewDialog Dieses Steuerelement zeigt die Vorschau auf ein zu druckendes Dokument. Obwohl es ein Standarddialogfeld ist, folgt es nicht dem gleichen Vererbungspfad wie die anderen Standarddialogfelder. Vielmehr erbt es direkt von der Form-Klasse. Tabelle B.33 führt die Eigenschaften dieses Steuerelements auf. Eigenschaft
Beschreibung
Document
Das Dokument, das in der Vorschau angezeigt werden soll.
PrintPreviewControl Liefert das in diesem Steuerelement enthaltene PrintPreviewControl. UseAntiAlias
Gibt an, ob die Vorschau für den Text Antialiasing verwenden soll, um so eine glatter aussehende Vorschau zu ermöglichen.
Tabelle 1.33: Eigenschaften von PrintPreviewDialog
PropertyGrid Dieses Steuerelement stellt eine Benutzeroberfläche für das Durchsuchen von Eigenschaften eines Objekts zur Verfügung. Es erbt direkt von ContainerControl und indirekt von ScrollableControl. Tabelle B.34 führt die Eigenschaften, Methoden und Ereignisse dieses Steuerelements auf. Eigenschaft
Beschreibung
BrowsableAttributes Eine AttributeCollection, die die Attribute des zu durchsuchenden Objekts
enthält. CanShowCommands
Gibt an, ob Befehle für das zu durchsuchende Objekt angezeigt werden können.
CommandsBackColor
Die Hintergrundfarbe des Befehlsbereichs.
CommandsForeColor
Die Vordergrundfarbe des Befehlsbereichs.
CommandsVisible
Gibt an, ob der Befehlsbereich sichtbar ist.
CommandsVisibleIfAvailable
Gibt an, ob der Befehlsbereich sichtbar ist für Objekte, die Befehle offen legen.
ContextMenuDefault- Die Standardposition für das Kontextmenü. Location
Tabelle 1.34: Eigenschaften, Methoden und Ereignisse von PropertyGrid
40
Bonuskapitel 1
Eigenschaft
Beschreibung
HelpBackColor
Die Hintergrundfarbe des Hilfebereichs.
HelpForeColor
Die Vordergrundfarbe des Hilfebereichs.
HelpVisible
Gibt an, ob der Hilfebereich sichtbar ist.
LargeButtons
Gibt an, ob das Steuerelement Schaltflächen größer als vom Standard vorgegeben anzeigen soll.
LineColor
Die Farbe der Datenblatt- und Rahmenlinien.
PropertySort
Der Typ der vom Steuerelement verwendeten Sortierung; verwendet Werte der PropertySort-Aufzählung.
PropertyTabs
Eine Auflistung von Registerkarten für Eigenschaften dieses Steuerelements.
SelectedGridItem
Das aktuell ausgewählte Element.
SelectedObject
Das Objekt, für das dieses Steuerelement Eigenschaften anzeigt.
SelectedObjects
Die aktuell ausgewählten Objekte.
SelectedTab
Die aktuell ausgewählte Registerkarte für Eigenschaften.
ToolbarVisible
Gibt an, ob die Symbolleiste sichtbar ist.
ViewBackColor
Die Hintergrundfarbe des Datenblattes.
ViewForeColor
Die Vordergrundfarbe des Datenblattes.
Methode
Beschreibung
CollapseAllGridItems
Reduziert alle Kategorien im Datenblatt.
ExpandAllGridItems
Erweitert alle Kategorien im Datenblatt.
RefreshTabs
Aktualisiert die Registerkarten für Eigenschaften des Datenblattes.
Ereignis
Beschreibung
PropertySortChanged Wird ausgelöst, wenn sich der Sortiermodus ändert. PropertyTabChanged
Wird ausgelöst, wenn sich eine Registerkarte für Eigenschaften ändert.
PropertyValueChanged
Wird ausgelöst, wenn sich ein Eigenschaftenwert ändert.
Tabelle 1.34: Eigenschaften, Methoden und Ereignisse von PropertyGrid (Forts.)
41
Bonuskapitel 1
Ereignis
Beschreibung
SelectedGridItemChanged
Wird ausgelöst, wenn sich das ausgewählte Element ändert.
SelectedObjectsChanged
Wird ausgelöst, wenn sich die SelectedObjects-Eigenschaft ändert.
Tabelle 1.34: Eigenschaften, Methoden und Ereignisse von PropertyGrid (Forts.)
TabPage Dieses Steuerelement ist beinahe identisch mit dem Panel-Steuerelement, nur dass es sich ausschließlich innerhalb eines TabControl-Steuerelements verwenden lässt und eine bestimmte Tabseite repräsentiert. TabPage ist direkt von Panel abgeleitet. Tabelle B.35 führt die Eigenschaften dieses Steuerelements auf. Eigenschaft
Beschreibung
ImageIndex
Der Index der auf der Tabseite anzuzeigenden Grafik; schlagen Sie die ImageList-Eigenschaft von TabControl nach, um mehr Informationen zu erhalten.
ToolTipText
Der QuickInfo-Text für dieses Steuerelement.
Tabelle 1.35: Eigenschaften von TabPage
1.9
ErrorProvider
ErrorProvider erlaubt die Darstellung einer Benutzeroberfläche, damit der Benutzer einen Fehler, der in Zusammenhang mit einem Steuerelement auftritt, beschreiben kann. Es ist jedoch nicht von Control abgeleitet. Tabelle B.36 führt die Eigenschaften und Methoden dieses Steuerelements auf. Eigenschaft
Beschreibung
BlinkRate
Die Frequenz, mit der das Fehlersymbol blinkt.
BlinkStyle
Der Darstellungsstil, in dem das Fehlersymbol blinkt; verwendet Werte der ErrorBlinkStyle-Aufzählung.
Tabelle 1.36: Eigenschaften und Methoden von ErrorProvider
42
Bonuskapitel 1
Eigenschaft
Beschreibung
ContainerControl
Das übergeordnete Steuerelement für dieses Steuerelement.
DataMember
Die zu überwachende Datentabelle.
DataSource
Das zu überwachende Dataset.
Icon
Das im Steuerelement anzuzeigende Symbol.
Methode
Beschreibung
BindToDataAndErrors Stellt eine Möglichkeit zur Verfügung, die DataMember- und DataSource-
Eigenschaften festzulegen. CanExtend
Gibt an, ob sich das Steuerelement erweitern lässt.
GetError
Die Fehlerbeschreibung für ein angegebenes Steuerelement.
GetIconAlignment
Gibt an, wo das Symbol im Verhältnis zum Text im Steuerelement platziert werden soll; verwendet Werte der ErrorIconAlignment-Aufzählung.
GetIconPadding
Der rund um das Fehlersymbol anzuzeigende freie Bereich.
SetError
Legt die Fehlerbeschreibungszeichenfolge für ein angegebenes Steuerelement fest.
SetIconAlignment
Wird benutzt, um festzulegen, wo das Symbol im Verhältnis zum Text im Steuerelement platziert werden soll; verwendet Werte der ErrorIconAlignment-Aufzählung.
SetIconPadding
Legt den freien Bereich fest, der rund um das Fehlersymbol anzuzeigen ist.
UpdateBinding
Aktualisiert die Bindungen von DataSource, DataMember und des Fehlerbeschreibungstextes.
Tabelle 1.36: Eigenschaften und Methoden von ErrorProvider(Forts.)
1.10 GroupBox Dieses Steuerelement repräsentiert einen gruppierten Abschnitt (oder Kasten) anderer Steuerelemente. Tabelle B.37 zeigt die Eigenschaft dieses Steuerelements.
43
Bonuskapitel 1
Eigenschaft
Beschreibung
FlatStyle
Die 3D-Darstellung des Steuerelements; verwendet Werte aus der FlatStyleAufzählung.
Tabelle 1.37: Eigenschaft von GroupBox
1.11 Bildlaufleisten Bildlaufleisten sind Steuerelemente, mit denen man in einem Steuerelement navigieren kann. Während einige Steuerelemente über eingebaute Bildlaufleisten verfügen, kann man die Steuerelemente in diesem Abschnitt dazu einsetzen, weitere Bildlaufleisten jedem beliebigen Steuerelement hinzuzufügen. Alle Steuerelemente in diesem Abschnitt sind von der ScrollBar-Klasse (siehe Tabelle B.38) abgeleitet, die wiederum direkt von Control erbt. Eigenschaft
Beschreibung
LargeChange
Der Wert, der von der Value-Eigenschaft zu subtrahieren bzw. zur ValueEigenschaft zu addieren ist, wenn das Bildlauffeld über eine größere Entfernung verschoben wird (normalerweise wenn der Benutzer in die Bildlaufleiste über oder unter dem Bildlauffeld klickt).
Maximum
Der maximale Wert der Value-Eigenschaft.
Minimum
Der minimale Wert der Value-Eigenschaft.
SmallChange
Der Wert, der von der Value-Eigenschaft zu subtrahieren bzw. zur ValueEigenschaft zu addieren ist, wenn das Bildlauffeld über eine kurze Entfernung verschoben wird (normalerweise wenn der Benutzer auf die Pfeile der Bildlaufleisten klickt oder direkt das Bildlauffeld bewegt).
Value
Ein numerischer Wert, der die aktuelle Position des Bildlauffeldes repräsentiert.
Ereignis
Beschreibung
Scroll
Wird ausgelöst, wenn das Bildlauffeld bewegt wurde.
ValueChanged
Wird ausgelöst, wenn sich die Value-Eigenschaft geändert hat.
Tabelle 1.38: Eigenschaften und Ereignisse von ScrollBar
44
Bonuskapitel 1
HScrollBar Dieses Steuerelement repräsentiert eine horizontale Bildlaufleiste in einem Steuerelement. Es ist direkt von der ScrollBar-Klasse abgeleitet und enthält keinerlei Eigenschaften, Methoden oder Ereignisse, die nicht geerbt werden.
VScrollBar Dieses Steuerelement repräsentiert eine vertikale Bildlaufleiste in einem Steuerelement. Es ist direkt von der ScrollBar-Klasse abgeleitet und enthält keinerlei Eigenschaften, Methoden oder Ereignisse, die nicht geerbt werden.
1.12 ImageList Das ImageList-Steuerelement repräsentiert eine Auflistung von Image-Objekten, die sich einem anderen Steuerelement zuweisen lässt. Es ist nicht von der Control-Klasse abgeleitet. Tabelle B.39 führt Eigenschaften, Methode und Ereignis dieses Steuerelements auf. Eigenschaft
Beschreibung
ColorDepth
Die Farbtiefe der Grafiken in der Liste.
Handle
Der Handle dieses Steuerelements.
HandleCreated
Gibt an, ob der Handle für dieses Steuerelement erzeugt worden ist.
Images
Ein ImageCollection-Objekt, das die Grafiken in der Liste repräsentiert.
ImageSize
Die Größe der Grafiken in der Liste.
ImageStream
Der Handle für den ImageListStreamer, der mit dieser Liste verknüpft ist.
TransparentColor
Die Farbe in den Grafiken, die als transparent behandelt werden soll.
Methode
Beschreibung
Draw
Zeichnet die angegebene Grafik.
Ereignis
Beschreibung
RecreateHandle
Wird ausgelöst, wenn der Handle erzeugt wird.
Tabelle 1.39: Eigenschaften, Methode und Ereignis von ImageList
45
Bonuskapitel 1
1.13 Label-Steuerelemente Bezeichnungsfelder stellen eine Möglichkeit dar, dem Benutzer statischen Text anzuzeigen. Es gibt zwei Label-Steuerelemente im .NET Framework: Label und LinkLabel. Tabelle B.40 führt die Eigenschaften und Ereignisse des Label-Steuerelements auf. Eigenschaft
Beschreibung
AutoSize
Gibt an, ob das Steuerelement seine Größe automatisch der Größe der enthaltenen Schriftart anpassen soll.
BorderStyle
Der für das Steuerelement zu benutzende Rahmenstil; verwendet Werte aus der BorderStyle-Aufzählung.
FlatStyle
Die 3D-Darstellung des Steuerelements; verwendet Werte aus der FlatStyleAufzählung.
Image
Die Grafik, die auf diesem Steuerelement angezeigt wird.
ImageAlign
Die Ausrichtung der Grafik, die mit der Image-Eigenschaft angezeigt wird; verwendet einen Wert aus der ContentAlignment-Aufzählung.
ImageIndex
Der Index der anzuzeigenden Grafik; vgl. die ImageList-Eigenschaft.
ImageList
Die ImageList, die mit diesem Steuerelement verknüpft ist.
PreferredHeight
Die bevorzugte Höhe des Steuerelements.
PreferredWidth
Die bevorzugte Breite des Steuerelements.
TextAlign
Gibt die Ausrichtung des Textes im Steuerelement an; verwendet Werte der ContentAlignment-Aufzählung.
UseMnemonic
Gibt an, ob das Steuerelement ein kaufmännisches Und-Zeichen (&) als Präfix für eine Zugriffstaste interpretiert.
Ereignis
Beschreibung
AutoSizeChanged
Wird ausgelöst, wenn sich die AutoSize-Eigenschaft ändert.
TextAlignChanged
Wird ausgelöst, wenn sich die TextAlign-Eigenschaft ändert.
Tabelle 1.40: Eigenschaften und Ereignisse von Label
Das LinkLabel-Steuerelement ähnelt dem Label-Steuerelement, nur dass es auch Hyperlinks darstellen kann. Es ist direkt von der Label-Klasse abgeleitet. Tabelle B.41 führt Eigenschaften und Ereignis dieses Steuerelements auf.
46
Bonuskapitel 1
Eigenschaft
Beschreibung
ActiveLinkColor
Die Farbe eines aktiven Hyperlinks.
DisabledLinkColor
Die Farbe eines deaktivierten Hyperlinks.
LinkArea
Der Textbereich im Steuerelement, der als Hyperlink zu behandeln ist.
LinkBehavior
Die Verhaltensweise des Hyperlinks; verwendet Werte aus der LinkBehaviorAufzählung.
LinkColor
Die Farbe eines normalen Hyperlinks.
Links
Eine Auflistung von Hyperlinks in diesem Steuerelement.
LinkVisited
Gibt an, ob ein bestimmter Hyperlink als bereits besuchter Hyperlink angezeigt werden soll.
VisitedLinkColor
Die Farbe eines bereits besuchten Hyperlinks.
Ereignis
Beschreibung
LinkClicked
Wird ausgelöst, wenn ein Hyperlink im Steuerelement angeklickt wird.
Tabelle 1.41: Eigenschaften und Ereignis des LinkLabel-Steuerelements
1.14 ListView Dieses Steuerelement zeigt eine Auflistung von Elementen an, und zwar auf vier verschiedene Weisen, ähnlich wie der Windows Explorer. Tabelle B.42 führt die Eigenschaften, Methoden und Ereignisse dieses Steuerelements auf. Eigenschaft
Beschreibung
Activation
Die Art der Aktion, die ein Benutzer ausführen muss, um ein Element zu aktivieren; verwendet einen Wert aus der ItemActivation-Aufzählung.
Alignment
Die Ausrichtung von Elementen im Steuerelement; verwendet Werte aus der ListViewAlignment-Aufzählung.
AllowColumnReorder
Gibt an, ob der Benutzer Spalten im Steuerelement umstellen kann, indem er die Spaltenköpfe auf neue Positionen zieht.
AutoArrange
Gibt an, ob die Elemente automatisch arrangiert werden.
Tabelle 1.42: Eigenschaften, Methoden und Ereignisse von ListView
47
Bonuskapitel 1
Eigenschaft
Beschreibung
BorderStyle
Der Rahmenstil des Steuerelements; verwendet Werte aus der BorderStyleAufzählung.
CheckBoxes
Gibt an, ob Kontrollkästchen neben jedem Element im Steuerelement erscheinen sollen.
CheckedIndices
Die nullbasierten Indizes der aktuell aktivierten Elemente im Steuerelement.
CheckedItems
Die aktuell aktivierten Elemente im Steuerelement.
Columns
Die Auflistung aller Spalten, die im Steuerelement erscheinen.
FocusedItem
Das Element, das gerade den Eingabefokus besitzt.
FullRowSelect
Gibt an, ob ein Klick auf ein Element all seine Unterelemente auswählt.
GridLines
Gibt an, ob Datenblattlinien gezeichnet werden sollen.
HeaderStyle
Der Darstellungsstil von Spaltenköpfen; verwendet Werte aus der ColumnHeaderStyle-Aufzählung.
HideSelection
Gibt an, ob das ausgewählte Element im Steuerelement selbst dann markiert bleibt, wenn das Steuerelement den Fokus verliert.
HoverSelection
Gibt an, ob ein Element automatisch ausgewählt wird, wenn der Mauszeiger über dem Element schwebt.
Items
Die Auflistung aller Elemente in der Liste.
LabelEdit
Gibt an, ob der Benutzer die Bezeichnung jedes Elements im Steuerelement bearbeiten kann.
LabelWrap
Gibt an, ob Bezeichnungen umgebrochen werden sollen, wenn Elemente als Symbole angezeigt werden.
LargeImageList
Das zu benutzende ImageList-Steuerelement, wenn Elemente als große Symbole dargestellt werden.
ListViewItemSorter
Der Sortiervergleich für das Steuerelement
MultiSelect
Gibt an, ob mehrere Elemente auf einmal ausgewählt werden können.
Scrollable
Gibt an, ob Bildlaufleisten automatisch nach Bedarf hinzugefügt werden.
SelectedIndices
Die nullbasierten Indizes der ausgewählten Elemente im Steuerelement.
Tabelle 1.42: Eigenschaften, Methoden und Ereignisse von ListView (Forts.)
48
Bonuskapitel 1
Eigenschaft
Beschreibung
SelectedItems
Die aktuell ausgewählten Elemente im Steuerelement.
SmallImageList
Das zu verwendende ImageList-Steuerelement, wenn Elemente als kleine Symbole angezeigt werden.
Sorting
Die Sortierreihenfolge für Elemente im Steuerelement; verwendet Werte aus der SortOrder-Aufzählung.
StateImageList
Das ImageList-Steuerelement, das mit anwendungsdefinierten Zuständen im Steuerelement verknüpft ist.
TopItem
Das erste sichtbare Element im Steuerelement.
View
Gibt an, wie Elemente im Steuerelement angezeigt werden sollen; verwendet Werte aus der View-Aufzählung.
Methode
Beschreibung
ArrangeIcons
Arrangiert Elemente im Steuerelement, wenn sie als Symbole angezeigt werden.
BeginUpdate
Hindert das Steuerelement daran, sich selbst jedes Mal neu zu zeichnen, wenn ein neues Element hinzugefügt wird, bis EndUpdate aufgerufen wird.
Clear
Entfernt alle Elemente und Spalten aus dem Steuerelement.
EndUpdate
Nimmt das Zeichnen des Steuerelements wieder auf, nachdem BeginUpdate aufgerufen worden ist.
EnsureVisible
Sorgt dafür, dass das aktuell ausgewählte Element im Steuerelement sichtbar ist; führt ggf. einen Bildlauf durch den Inhalt des Steuerelements durch.
GetItemAt
Liefert das Element an der angegebenen Position.
GetItemRect
Liefert das umschließende Rechteck für ein angegebenes Element.
Sort
Sortiert die Elemente im Steuerelement.
Ereignis
Beschreibung
AfterLabelEdit
Wird ausgelöst, nachdem der Benutzer die Bezeichnung eines Elements bearbeitet hat.
BeforeLabelEdit
Wird ausgelöst, bevor der Benutzer die Bezeichnung eines Elements bearbeitet.
ColumnClick
Wird ausgelöst, wenn der Benutzer auf einen Spaltenkopf klickt.
Tabelle 1.42: Eigenschaften, Methoden und Ereignisse von ListView (Forts.)
49
Bonuskapitel 1
Ereignis
Beschreibung
ItemActivate
Wird ausgelöst, wenn ein Element aktiviert wird.
ItemCheck
Wird ausgelöst, wenn ein Element aktiviert oder deaktiviert wird.
ItemDrag
Wird ausgelöst, wenn der Benutzer beginnt, ein Element zu ziehen.
SelectedIndexChanged
Wird ausgelöst, wenn sich der Index des ausgewählten Elements ändert.
Tabelle 1.42: Eigenschaften, Methoden und Ereignisse von ListView (Forts.)
1.15 PictureBox Das Steuerelement zeigt eine Grafik an. Tabelle B.43 führt Eigenschaften und Ereignis dieses Steuerelements auf. Eigenschaft
Beschreibung
BorderStyle
Der Rahmendarstellungsstil für das Steuerelement; verwendet Werte aus der BorderStyle-Aufzählung.
Image
Die im Steuerelement anzuzeigende Grafik.
SizeMode
Gibt an, wie die Grafik anzuzeigen ist; verwendet Werte der PictureBoxSizeMode-Aufzählung.
Ereignis
Beschreibung
SizeModeChanged
Wird ausgelöst, wenn sich die SizeMode-Eigenschaft ändert.
Tabelle 1.43: Eigenschaften und Ereignis von PictureBox
1.16 PrintDocument Dieses Steuerelement repräsentiert ein Dokument, das an einen Drucker geschickt wird, um gedruckt zu werden. Es erbt nicht von der Control-Klasse. Tabelle B.44 führt Eigenschaften, Methode und Ereignisse dieses Steuerelements auf.
50
Bonuskapitel 1
Eigenschaft
Beschreibung
DefaultPageSettings Das PageSettings-Objekt, das die Voreinstellungen für alle gedruckten Sei-
ten zur Verfügung stellt. DocumentName
Der Name des zu druckenden Dokuments.
PrintController
Der Druckercontroller, der den Druckvorgang steuert.
PrinterSettings
Das PrinterSettings-Objekt, das den Drucker repräsentiert, der für den Druck verwendet werden soll.
Methode
Beschreibung
Print
Startet den Druckvorgang für das Dokument.
Ereignis
Beschreibung
BeginPrint
Wird ausgelöst, wenn die Print-Methode aufgerufen wird, jedoch noch bevor irgendwelche Seiten gedruckt worden sind.
EndPrint
Wird ausgelöst, wenn die letzte Seite des Dokuments gedruckt worden ist.
PrintPage
Wird jedes Mal ausgelöst, wenn eine Seite zu drucken ist.
QueryPageSettings
Wird direkt vor jedem PrintPage-Ereignis ausgelöst.
Tabelle 1.44: Eigenschaften, Methode und Ereignisse von PrintDocument
1.17 PrintPreviewControl Dieses Steuerelement repräsentiert den eigentlichen Vorschaubereich des PrintPreviewDialog-Dialogfeldes. Man beachte, dass sich dieses Steuerelement selbstständig außerhalb von PrintPreviewDialog benutzen lässt. Tabelle B.45 führt die Eigenschaften und Methode dieses Steuerelements auf. Eigenschaft
Beschreibung
AutoZoom
Gibt an, ob eine Größenänderung des Steuerelements oder eine Änderung der Anzahl von angezeigten Seiten automatisch die Zoom-Eigenschaft anpasst.
Columns
Die Anzahl der Vorschauseiten, die horizontal im Steuerelement angezeigt werden.
Tabelle 1.45: Eigenschaften und Methode des PrintPreviewControl-Steuerelements
51
Bonuskapitel 1
Eigenschaft
Beschreibung
Document
Das PrintDocument für die Vorschau.
Rows
Die Anzahl von Vorschauseiten, die vertikal im Steuerelement angezeigt werden.
StartPage
Die Seitennummer der Seite, die im Steuerelement oben links angezeigt wird.
UseAntiAlias
Gibt an, ob das Steuerelement für die Vorschaudarstellung Antialiasing einsetzen soll, um so eine glattere Grafik zu zeigen.
Zoom
Gibt an, wie groß die Vorschauseiten im Steuerelement sein sollen.
Methode
Beschreibung
InvalidatePreview
Aktualisiert die Ansicht der Vorschau.
StartPageChanged
Wird ausgelöst, wenn sich die StartPage-Eigenschaft ändert.
Tabelle 1.45: Eigenschaften und Methode des PrintPreviewControl-Steuerelements (Forts.)
1.18 ProgressBar Das Steuerelement repräsentiert eine Windows-Statusanzeige. Tabelle B.46 führt die Eigenschaften und Methoden dieses Steuerelements auf. Eigenschaft
Beschreibung
Maximum
Der maximale Wert oder Bereich des Steuerelements.
Minimum
Der minimale Wert oder Bereich des Steuerelements.
Step
Der Betrag, um den sich die Position der Statusanzeige erhöhen soll, wenn die PerformStep-Methode aufgerufen wird.
Value
Die aktuelle Position der Statusanzeige.
Methode
Beschreibung
Increment
Erhöht die Position der Statusanzeige um den angegebenen Betrag.
PerformStep
Erhöht die Position der Statusanzeige um den Betrag in der Step-Eigenschaft.
Tabelle 1.46: Eigenschaften und Methoden von ProgressBar
52
Bonuskapitel 1
1.19 Textfeld-Steuerelemente Es gibt zwei Textfeld-Steuerelemente im .NET Framework: TextBox und RichTextBox. Beide Steuerelemente erben von der in Tabelle B.47 gezeigten TextBoxBase-Klasse, welche wiederum von Control erbt. Eigenschaft
Beschreibung
AcceptsTab
Gibt an, ob ein Druck auf die (ÿ_)-Taste ein Tabstoppzeichen in das Steuerelement eingibt oder den Fokus auf das nächste Steuerelement in der Aktivierreihenfolge setzt.
AutoSize
Gibt an, ob das Steuerelement automatisch seine Größe anpassen soll, wenn sich die Font-Eigenschaft ändert.
BorderStyle
Der Rahmendarstellungsstil des Steuerelements; verwendet Werte der BorderStyle-Aufzählung.
CanUndo
Gibt an, ob der Benutzer einen vorangegangenen Arbeitsschritt rückgängig machen kann.
HideSelection
Gibt an, ob markierter Text markiert bleiben soll, wenn das Steuerelement den Fokus verliert.
Lines
Ein Array von Zeichenfolgen, die die Zeilen im Steuerelement repräsentieren.
MaxLength
Die maximale Anzahl von Zeichen, die sich in das Steuerelement eingeben lassen.
Modified
Gibt an, ob das Steuerelement geändert wurde seit seine Inhalte erstmals festgelegt wurden.
Multiline
Gibt an, ob das Steuerelement mehrere Zeilen von Text enthalten kann.
PreferredHeight
Die bevorzugte Höhe des Steuerelements.
ReadOnly
Gibt an, ob sich die Inhalte des Steuerelements bearbeiten lassen.
SelectedText
Liefert oder setzt den aktuell im Steuerelement markierten Text.
SelectionLength
Die Anzahl von markierten Zeichen im Steuerelement.
SelectionStart
Der nullbasierte Index des Zeichens, bei dem die Markierung anfängt.
TextLength
Die Länge des Textes im Steuerelement.
Tabelle 1.47: Eigenschaften, Methoden und Ereignisse von TextBoxBase
53
Bonuskapitel 1
Eigenschaft
Beschreibung
WordWrap
Gibt an, ob ein mehrzeiliges Textfeld automatisch Zeilen, die zu lang für dessen Anzeige sind, umbrechen soll.
Methode
Beschreibung
AppendText
Hängt Text an das Ende des Steuerelements an.
Clear
Entfernt allen Text aus dem Steuerelement.
ClearUndo
Löscht Informationen über die letzten Arbeitsschritte aus dem RückgängigPuffer.
Copy
Kopiert die aktuelle Auswahl in die Zwischenablage.
Cut
Entfernt die aktuelle Auswahl aus dem Steuerelement und legt sie in der Zwischenablage ab.
Paste
Fügt die Inhalte der Zwischenablage an der aktuellen Position in das Steuerelement ein.
ScrollToCaret
Führt einen Bildlauf durch den Inhalt des Steuerelements bis zur Einfügemarke (das Caret) durch.
Select
Markiert Text im Steuerelement.
SelectAll
Markiert den gesamten Text im Steuerelement.
Undo
Macht den letzten Arbeitsschritt im Steuerelement rückgängig.
Ereignis
Beschreibung
AcceptsTabChanged
Wird ausgelöst, wenn sich die AcceptsTab-Eigenschaft ändert.
AutoSizeChanged
Wird ausgelöst, wenn sich die AutoSize-Eigenschaft ändert.
BorderStyleChanged
Wird ausgelöst, wenn sich die BorderStyle-Eigenschaft ändert.
Click
Wird ausgelöst, wenn das Steuerelement angeklickt wird.
HideSelectionChanged
Wird ausgelöst, wenn sich die HideSelection-Eigenschaft ändert.
ModifiedChanged
Wird ausgelöst, wenn sich die Modified-Eigenschaft ändert.
MultilineChanged
Wird ausgelöst, wenn sich die Multiline-Eigenschaft ändert.
ReadOnlyChanged
Wird ausgelöst, wenn sich die ReadOnly-Eigenschaft ändert.
Tabelle 1.47: Eigenschaften, Methoden und Ereignisse von TextBoxBase (Forts.)
54
Bonuskapitel 1
RichTextBox Dieses Steuerelement repräsentiert ein Textfenster, das mit Rich Text (.RTF) formatierte Zeichen enthalten kann. Tabelle B.48 führt die Eigenschaften, Methoden und Ereignisse dieses Steuerelements auf. Eigenschaft
Beschreibung
AutoWordSelection
Gibt an, ob ein ganzes Wort automatisch ausgewählt wird, wenn eines seiner Zeichen ausgewählt wurde.
BulletIndent
Die Anzahl von Bildpunkten, um die ein Aufzählungssymbol im Steuerelement eingerückt werden soll.
CanRedo
Gibt an, ob es Arbeitsschritte gibt, die erneut angewendet werden können.
DetectUrls
Gibt an, ob das Steuerelement automatisch einen URL formatieren soll, wenn er in das Steuerelement eingegeben wird.
RedoActionName
Der Name des letzten Arbeitsschrittes, der sich erneut auf das Steuerelement anwenden ließe.
RightMargin
Der rechte Rand des editierbaren Bereichs des Steuerelements; bestimmt, wie viele Zeichen in eine Zeile passen.
Rtf
Der im Steuerelement enthaltene Text, einschließlich aller Rich Text-Formatierungscodes.
ScrollBars
Gibt an, wie Bildlaufleisten im Steuerelement gezeigt werden sollen; verwendet Werte der RichTextScrollBars-Aufzählung.
SelectedRtf
Der aktuell ausgewählte Text im Steuerelement, einschließlich aller Rich Text-Formatierungscodes.
SelectionAlignment
Die Ausrichtung, die auf den aktuell ausgewählten Text anzuwenden ist; verwendet Werte aus der HorizontalAlignment-Aufzählung.
SelectionBullet
Gibt an, ob ein Aufzählungssymbol auf die aktuelle Auswahl angewendet wird.
SelectionCharOffset Gibt an, ob der ausgewählte Text auf der Grundlinie, hochgestellt oder tief-
gestellt erscheinen soll. SelectionColor
Die Farbe markierten Textes.
SelectionFont
Die Schriftart des aktuell ausgewählten Textes.
Tabelle 1.48: Eigenschaften, Methoden und Ereignisse von RichTextBox
55
Bonuskapitel 1
Eigenschaft
Beschreibung
SelectionHangingIndent
Der Abstand zwischen dem linken Rand der ersten Textzeile im ausgewählten Absatz und dem linken Rand nachfolgender Zeilen im gleichen Absatz (hängender Einzug).
SelectionIndent
Der Abstand in Bildpunkten, um den die aktuelle Auswahl eingerückt werden soll.
SelectionProtected
Gibt an, ob die aktuelle Auswahl geschützt ist; wird solcher Text bearbeitet, wird das Protected-Ereignis ausgelöst.
SelectionRightIndent
Der Abstand in Bildpunkten zwischen dem rechten Rand des Steuerelements und dem rechten Rand des aktuell ausgewählten Textes.
SelectionTabs
Die Positionen der Tabstopps im Steuerelement.
SelectionType
Der Typ der aktuellen Auswahl; verwendet Werte aus der RichTextBoxSelectionTypes-Aufzählung.
ShowSelectionMargin Gibt an, ob ein linker Rand, mit dem der Benutzer mehrere Zeilen auf ein-
mal auswählen kann, angezeigt werden soll. UndoActionName
Der Name des letzten Arbeitsschrittes, der sich rückgängig machen lässt.
ZoomFactor
Der aktuelle Zoomfaktor des Steuerelements.
Methode
Beschreibung
CanPaste
Gibt an, ob man den Inhalt der Zwischenablage an der aktuellen Position im Steuerelement einfügen kann.
Find
Sucht nach Text im Steuerelement.
GetCharFromPosition Liefert das Zeichen, das sich dem angegebenen Punkt am nächsten befindet. GetCharIndexFromPosition
Liefert den nullbasierten Zeichenindex, der sich dem angegebenen Punkt am nächsten befindet.
GetLineFromCharIndex
Liefert die Zeile, in der sich der angegebene Zeichenindex befindet.
LoadFile
Lädt die Inhalte einer Datei in das Steuerelement.
Redo
Wendet den letzten im Steuerelement ausgeführten Arbeitsschritt erneut an.
SaveFile
Speichert die Inhalte des Steuerelements in eine Datei.
Tabelle 1.48: Eigenschaften, Methoden und Ereignisse von RichTextBox (Forts.)
56
Bonuskapitel 1
Ereignis
Beschreibung
ContentsResized
Wird ausgelöst, wenn die Inhalte des Steuerelements in der Größe geändert werden.
HScroll
Wird ausgelöst, wenn der Benutzer einen horizontalen Bildlauf durch das Steuerelement ausführt.
ImeChange
Wird ausgelöst, wenn der Benutzer die Eingabemethoden in asiatischen Versionen von Windows wechselt.
LinkClicked
Wird ausgelöst, wenn der Benutzer im Steuerelement auf einen Hyperlink klickt.
Protected
Wird ausgelöst, wenn der Benutzer versucht, eine Auswahl, die mit der Protected-Eigenschaft markiert ist, zu bearbeiten.
SelectionChanged
Wird ausgelöst, wenn sich die aktuelle Auswahl im Steuerelement ändert.
VScroll
Wird ausgelöst, wenn der Benutzer einen vertikalen Bildlauf durch das Steuerelement ausführt.
Tabelle 1.48: Eigenschaften, Methoden und Ereignisse von RichTextBox (Forts.)
TextBox Dieses Steuerelement repräsentiert ein einfaches Textfenster, das reinen Text enthalten kann, jedoch ohne Formatierung. Tabelle B.49 führt Eigenschaften und Ereignis dieses Steuerelements auf. Eigenschaft
Beschreibung
AcceptsReturn
Gibt an, ob ein Druck auf die (¢)-Taste eine neue Zeile in das Steuerelement einfügt statt die Standardschaltfläche des umgebenden Formulars zu aktivieren.
CharacterCasing
Gibt an, ob das Steuerelement die Groß-/Kleinschreibung von Zeichen ändert, während man sie eingibt; verwendet Werte der CharacterCasing-Aufzählung.
PasswordChar
Das Zeichen, das zu benutzen ist, um die eingetippten Zeichen eines Kennwortes zu maskieren.
ScrollBars
Gibt an, ob Bildlaufleisten in einem mehrzeiligen Textfeld anzuzeigen sind; verwendet Werte aus der ScrollBars-Aufzählung.
Tabelle 1.49: Eigenschaften und Ereignis von TextBox
57
Bonuskapitel 1
Eigenschaft TextAlign
Beschreibung Die Ausrichtung von Text im Steuerelement; verwendet Werte aus der HorizontalAlignment-Aufzählung.
Ereignis
Beschreibung
TextAlignChanged
Wird ausgelöst, wenn sich die TextAlign-Eigenschaft ändert.
Tabelle 1.49: Eigenschaften und Ereignis von TextBox (Forts.)
1.20 Steuerelemente der Statusleiste Das StatusBar-Steuerelement repräsentiert eine Statusleiste in einer Windows-Anwendung. Es kann StatusBarPanel-Steuerelemente enthalten, die Daten anzeigen. Tabelle B.50 führt die Eigenschaften und Ereignisse dieses Steuerelements auf. Eigenschaft
Beschreibung
Panels
Ein Auflistung von StatusBar-Bereichen im Steuerelement.
ShowPanels
Gibt an, ob dieses Steuerelement enthaltene StatusBar-Bereiche anzeigen soll.
SizingGrip
Gibt an, ob in der unteren rechten Ecke der Statusleiste ein Größenziehpunkt angezeigt werden soll.
Ereignis
Beschreibung
DrawItem
Wird ausgelöst, wenn sich ein visueller Aspekt eines vom Besitzer gezeichneten Steuerelements ändert.
PanelClick
Wird ausgelöst, wenn der Benutzer auf eines der in diesem Steuerelement enthaltenen StatusBarPanel-Steuerelemente klickt.
Tabelle 1.50: Eigenschaften und Ereignisse von StatusBar
Tabelle B.51 führt die Eigenschaften des StatusBarPanel-Steuerelements auf. Dieses Steuerelement erbt nicht von der Control-Klasse.
58
Bonuskapitel 1
Eigenschaft
Beschreibung
Alignment
Die Ausrichtung der Inhalte innerhalb des Statusleistenbereichs; verwendet Werte aus der HorizontalAlignment-Aufzählung.
AutoSize
Gibt an, ob das Steuerelement automatisch seine Größe je nach der Größe des umgebenden StatusBar-Steuerelements anpassen sollte; verwendet Werte aus der StatusBarPanelAutoSize-Aufzählung.
BorderStyle
Der Rahmendarstellungsstil für dieses Steuerelement; verwendet Werte aus der BorderStyle-Aufzählung.
Icon
Das im Steuerelement anzuzeigende Symbol.
MinWidth
Die kleinste Breite des Statusleistenbereichs in Übereinstimmung mit dem umgebenden StatusBar-Steuerelement.
Parent
Das Eltern-StatusBar-Steuerelement.
Style
Der Darstellungsstil dieses Steuerelements; verwendet Werte aus der StatusBarPanel-Aufzählung.
Text
Der im Steuerelement anzuzeigende Text.
ToolTipText
Der als QuickInfo für dieses Steuerelement anzuzeigende Text.
Width
Die Breite des Steuerelements innerhalb des umgebenden StatusBar-Steuerelements.
Tabelle 1.51: Eigenschaften von StatusBarPanel
1.21 TabControl Ein TabControl repräsentiert die Struktur für eine Ansicht mit Registerkarten in einer Anwendung. Es enthält TabPage-Steuerelemente, die jeweils einzelne Registerkarten repräsentieren. Tabelle B.52 führt Eigenschaften, Methode und Ereignis des TabControl-Steuerelements auf. Eigenschaft
Beschreibung
Alignment
Die Ausrichtung von Registerkarten im Steuerelement; verwendet Werte der TabAlignment-Aufzählung.
Tabelle 1.52: Eigenschaften, Methode und Ereignis von TabControl
59
Bonuskapitel 1
Eigenschaft
Beschreibung
Appearance
Der Darstellungsstil Registerkarten; verwendet Werte aus der TabAppearanceAufzählung.
DrawMode
Gibt an, wie Tabseiten im Steuerelement gezeichnet werden; verwendet Werte aus der TabDrawMode-Aufzählung.
HotTrack
Gibt an, ob die Registerkarten ihr Aussehen verändern, wenn der Mauszeiger über sie bewegt wird.
ImageList
Ein ImageList-Steuerelement, das Grafiken für jede Registerkarte enthält.
ItemSize
Die Größe der Registerkarten des Steuerelements.
Multiline
Gibt an, ob Registerkarten in mehreren Zeilen erscheinen sollen.
Padding
Der Raum in Bildpunkten um jedes Element auf einer Tabseite.
RowCount
Die Anzahl der Zeilen mit Registerkarten im TabControl.
SelectedIndex
Der nullbasierte Index der aktuell ausgewählten Tabseite.
SelectedTab
Das aktuell ausgewählte TabPage-Objekt.
ShowToolTips
Gibt an, ob QuickInfos für eine Registerkarte anzuzeigen sind, wenn sich der Mauszeiger darüber bewegt.
SizeMode
Die Art und Weise, in der die Größe der Registerkarten verändert wird; verwendet Werte aus der TabSizeMode-Eigenschaft.
TabCount
Die Anzahl von Registerkarten in diesem Steuerelement.
TabPages
Eine Auflistung der TabPage-Steuerelemente in diesem Steuerelement.
Methode
Beschreibung
GetTabRect
Liefert das angrenzende Rechteck für die angegebene Registerkarte.
Ereignis
Beschreibung
DrawItem
Wird ausgelöst, wenn die Registerkarten gezeichnet werden und die DrawMode-Eigenschaft auf OwnerDrawFixed eingestellt ist.
Tabelle 1.52: Eigenschaften, Methode und Ereignis von TabControl (Forts.)
60
Bonuskapitel 1
1.22 Timer Timer repräsentiert einen Zeitgeber, der Ereignisse in einem angegebenen Zeitintervall auslöst. Dieses Steuerelement erbt nicht von Control. Tabelle B.53 führt Eigenschaften,
Methoden und Ereignis dieses Steuerelements auf. Eigenschaft
Beschreibung
Enabled
Gibt an, ob der Timer gerade läuft.
Interval
Das Zeitintervall, in dem der Timer das Tick-Ereignis auslösen soll.
Methode
Beschreibung
Start
Startet den Timer.
Stop
Stoppt den Timer.
Ereignis
Beschreibung
Tick
Wird ausgelöst, wenn das angegebene Zeitintervall verstrichen ist.
Tabelle 1.53: Eigenschaften, Methoden und Ereignis von Timer
1.23 Steuerelemente der Symbolleiste Dieses Steuerelement stellt die Struktur einer Windows-Symbolleiste dar. Es kann ToolBarButton-Steuerelemente enthalten, die die eigentlichen Schaltflächen auf der Symbolleiste repräsentieren. Tabelle B.54 führt die Eigenschaften und Ereignisse dieses Steuerelements auf. Eigenschaft
Beschreibung
Appearance
Der Stil der Symbolleiste; verwendet Werte aus der ToolBarAppearance-Aufzählung
AutoSize
Gibt an, ob die Symbolleiste automatisch ihre Größe an die enthaltenen Schaltflächen anpassen soll.
BorderStyle
Der Darstellungsstil des Rahmens für dieses Steuerelement; verwendet Werte aus der BorderStyle-Aufzählung.
Tabelle 1.54: Eigenschaften und Ereignisse von ToolBar
61
Bonuskapitel 1
Eigenschaft
Beschreibung
Buttons
Die Auflistung von ToolBarButton-Steuerelementen auf diesem Steuerelement.
ButtonSize
Die Größe der Schaltflächen in diesem Steuerelement.
Divider
Gibt an, ob die Symbolleiste einen Trennstrich zeigt.
DropDownArrows
Gibt an, ob Dropdown-Schaltflächen auf der Symbolleiste Dropdownpfeile anzeigen sollen.
ImageList
Das ImageList-Steuerelement, das benutzt wird, um Grafiken mit dieser Symbolleiste zu verknüpfen.
ImageSize
Die Größe der Grafiken in dem verknüpften ImageList-Steuerelement.
ShowToolTips
Gibt an, ob eine QuickInfo für jede Schaltfläche angezeigt werden soll.
TextAlign
Die Ausrichtung des Textes in jeder Schaltfläche im Verhältnis zur Grafik auf der Schaltfläche.
Wrappable
Gibt an, ob die Symbolleistenschaltflächen eine neue Zeile bilden, um alle verfügbaren Symbolleistenschaltflächen anzeigen zu können.
Ereignis
Beschreibung
ButtonClick
Wird ausgelöst, wenn ein ToolBarButton in diesem Steuerelement angeklickt wird.
ButtonDropDown
Wird ausgelöst, wenn ein ToolBarButton im Dropdown-Stil angeklickt wird.
Tabelle 1.54: Eigenschaften und Ereignisse von ToolBar (Forts.)
Das ToolBarButton-Steuerelement repräsentiert die Schaltflächen auf einer ToolBar und erbt nicht von der Control-Klasse. Tabelle B.55 führt die Eigenschaften dieses Steuerelements auf. Eigenschaft
Beschreibung
DropDownMenu
Das von Menu abgeleitete Steuerelement, das als Dropdown-Menü für diese Schaltfläche angezeigt werden soll.
Enabled
Gibt an, ob die Schaltfläche Benutzerinteraktion zulässt.
ImageIndex
Der Index der Grafik in der ImageList-Eigenschaft der umgebenden ToolBar.
Tabelle 1.55: Eigenschaften von ToolBarButton
62
Bonuskapitel 1
Eigenschaft
Beschreibung
Parent
Das umgebende ToolBar-Steuerelement.
PartialPush
Gibt an, ob eine Schaltfläche das Aussehen, nur teilweise gedrückt worden zu sein, anzeigen kann.
Pushed
Gibt an, ob eine Schaltfläche aussehen soll, als sei sie gedrückt worden.
Rectangle
Das umschließende Rechteck für diese Schaltfläche.
Style
Der Darstellungsstil dieser Schaltfläche; verwendet Werte aus der ToolBarButtonStyle-Aufzählung.
Tag
Das Objekt, das Daten über das Steuerelement enthält.
Text
Der Text, der auf der Symbolleistenschaltfläche angezeigt wird.
ToolTipText
Der QuickInfo-Text, der angezeigt werden soll, wenn der Mauszeiger über der Schaltfläche ruht.
Visible
Gibt an, ob die Schaltfläche angezeigt werden soll.
Tabelle 1.55: Eigenschaften von ToolBarButton (Forts.)
1.24 ToolTip Dieses Steuerelement repräsentiert ein Popup-Fenster, das erscheint, wenn der Mauspfeil des Benutzers über einem Steuerelement ruht. Dieses Fenster sollte hilfreiche Informationen über die Verwendung des jeweiligen Steuerelements anzeigen. ToolTip erbt nicht von der Control-Klasse. Tabelle B.56 führt die Eigenschaften und Methoden dieses Steuerelements auf. Eigenschaft
Beschreibung
Active
Gibt an, ob die QuickInfo gerade aktiv ist.
AutomaticDelay
Die automatische Verzögerung in Millisekunden für die QuickInfo; diese Eigenschaft setzt automatisch die Eigenschaften AutoPopDelay, InitialDelay und ReshowDelay.
AutoPopDelay
Der Zeitraum, für den die QuickInfo sichtbar bleibt.
Tabelle 1.56: Eigenschaften und Methoden des ToolTip-Steuerelements
63
Bonuskapitel 1
Eigenschaft
Beschreibung
InitialDelay
Der Zeitraum, für den der Mauszeiger über dem Steuerelement ruhen muss, bevor die QuickInfo erscheint.
ReshowDelay
Die Zeit, die zwischen dem Verschwinden der einen QuickInfo und dem Auftauchen einer anderen verstreichen muss.
ShowAlways
Gibt an, ob die QuickInfo anzuzeigen ist, wenn das übergeordnete Steuerelement nicht aktiv ist.
Methode
Beschreibung
GetToolTip
Liefert die QuickInfo, die mit dem angegebenen Steuerelement verknüpft ist.
RemoveAll
Entfernt den Text, der mit dieser QuickInfo verknüpft ist.
SetToolTip
Setzt den Text, der mit dieser QuickInfo verknüpft ist.
Tabelle 1.56: Eigenschaften und Methoden des ToolTip-Steuerelements (Forts.)
1.25 TrackBar Dieses Steuerelement repräsentiert eine Windows-Verlaufsleiste, ein bildlauffähiges Steuerelement, das gewisse Ähnlichkeit mit ScrollBar hat. Tabelle B.57 führt Eigenschaften, Methode und Ereignisse dieses Steuerelements auf. Eigenschaft
Beschreibung
AutoSize
Gibt an, ob die Verlaufsleiste automatisch in der Größe angepasst wird.
LargeChange
Der Wert, der von der Value-Eigenschaft abgezogen oder zu dieser addiert werden soll, wenn das Bildlauffeld über eine große Entfernung verschoben wird (normalerweise wenn der Benutzer die Bildlaufleiste über oder unter dem Bildlauffeld anklickt).
Maximum
Der höchste Wert der Value-Eigenschaft.
Minimum
Der kleinste Wert der Value-Eigenschaft.
Orientation
Gibt die horizontale oder vertikale Ausrichtung der Verlaufsleiste an; verwendet Werte aus der Orientation-Aufzählung.
Tabelle 1.57: Eigenschaften, Methode und Ereignisse von TrackBar
64
Bonuskapitel 1
Eigenschaft
Beschreibung
SmallChange
Der Wert, der von der Value-Eigenschaft abgezogen oder zu dieser addiert werden soll, wenn das Bildlauffeld über eine kleine Entfernung verschoben wird (normalerweise wenn der Benutzer die Pfeile auf der Bildlaufleiste anklickt oder das Bildlauffeld direkt verschiebt).
TickFrequency
Der Wert zwischen den Teilstrichen auf dem Steuerelement.
TickStyle
Der Stil von Telilstrichen auf dem Steuerelement; verwendet Werte aus der TickStyle-Aufzählung.
Value
Ein numerischer Wert, der die aktuelle Position des Bildlauffeldes repräsentiert.
Methode
Beschreibung
SetRange
Legt den kleinsten und höchsten Wert für die Verlaufsleiste fest.
Ereignis
Beschreibung
Scroll
Wird ausgelöst, wenn das Bildlauffeld verschoben wurde.
ValueChanged
Wird ausgelöst, wenn sich die Value-Eigenschaft geändert hat.
Tabelle 1.57: Eigenschaften, Methode und Ereignisse von TrackBar (Forts.)
1.26 TreeView-Steuerelemente Dieses Steuerelement stellt eine hierarchische Auflistung von bezeichneten Elementen dar. Es kann eine beliebige Anzahl von verschachtelten TreeNode-Steuerelementen enthalten, die jedes Element repräsentieren. Tabelle B.58 führt die Eigenschaften, Methoden, und Ereignisse dieses Steuerelement. Eigenschaft
Beschreibung
BorderStyle
Der Rahmendarstellungsstil für dieses Steuerelement; verwendet Werte der BorderStyle-Aufzählung.
CheckBoxes
Gibt an, ob Kontrollkästchen neben jedem Element in der Strukturansicht gezeichnet werden sollen.
FullRowSelect
Gibt an, ob die Auswahlmarkierung die gesamte Länge der Strukturansicht umspannt oder nur die Beschriftung des Knotens.
Tabelle 1.58: Eigenschaften, Methoden und Ereignisse von TreeView
65
Bonuskapitel 1
Eigenschaft
Beschreibung
HideSelection
Gibt an, ob ein ausgewähltes Element markiert bleiben soll, wenn das Steuerelement den Fokus verliert.
HotTracking
Gibt an, ob die Elemente in der Strukturansicht ihr Aussehen verändern sollen, sobald sich der Mauszeiger über sie bewegt.
ImageIndex
Der Index der Grafik im ImageList-Steuerelement, die mit dieser Strukturansicht als Standardgrafik für die Elemente verknüpft ist.
ImageList
Ein ImageList-Steuerelement, das Grafiken enthält, die für jedes Element in der Strukturansicht anzuzeigen sind.
Indent
Der Abstand in Bildpunkten, um den jede Knotenebene einzurücken ist.
ItemHeight
Die Höhe jedes Elements im Steuerelement.
LabelEdit
Gibt an, ob die Bezeichnungen für jedes Element bearbeitbar sind.
Nodes
Liefert eine Auflistung von TreeNode-Steuerelementen, die die Elemente in der Strukturansicht repräsentieren.
PathSeparator
Die Trennzeichenfolge, die der Pfad des Strukturknotens verwendet.
Scrollable
Gibt an, ob dieses Steuerelement bei Bedarf Bildlaufleisten anzeigt.
SelectedImageIndex
Der verknüpfte ImageList-Indexwert der Grafik, die für das aktuell ausgewählte Element im Steuerelement angezeigt wird.
SelectedNode
Der Strukturknoten (TreeNode), der aktuell ausgewählt ist.
ShowLines
Gibt an, ob Linien zwischen den Elementen in diesem Steuerelement gezeichnet werden.
ShowPlusMinus
Gibt an, ob Plus- und Minus-Zeichen neben jenen Elementen gezeigt werden, die über Unterelemente verfügen.
ShowRootLines
Gibt an, ob Linien zwischen Stammknoten der Strukturansicht gezogen werden.
Sorted
Gibt an, ob Elemente automatisch sortiert werden sollen.
TopNode
Das erste vollständig sichtbare Element im Steuerelement.
VisibleCount
Die Anzahl vollständig sichtbarer Elemente im Steuerelement.
Tabelle 1.58: Eigenschaften, Methoden und Ereignisse von TreeView (Forts.)
66
Bonuskapitel 1
Methode
Beschreibung
BeginUpdate
Hindert das Steuerelement daran, sich jedes Mal, wenn ein neues Element hinzugefügt wird, neu zu zeichnen, bis EndUpdate aufgerufen wird.
CollapseAll
Reduziert alle erweiterten Knoten in der Liste.
EndUpdate
Nimmt den Zeichenvorgang für das Steuerelement wieder auf, nachdem BeginUpdate aufgerufen wurde.
ExpandAll
Erweitert alle reduzierten Knoten in der Liste.
GetNodeAt
Liefert den Knoten, der dem angegebenen Punkt am nächsten liegt.
GetNodeCount
Liefert die Anzahl von Elementen in der Liste; umfasst auf Wunsch alle Strukturknoten in allen Teilstrukturen..
Ereignis
Beschreibung
AfterCheck
Wird ausgelöst, nachdem ein Kontrollkästchen neben einem Element aktiviert wurde.
AfterCollapse
Wird ausgelöst, nachdem ein Element reduziert wurde.
AfterExpand
Wird ausgelöst, nachdem ein Element erweitert wurde.
AfterLabelEdit
Wird ausgelöst, nachdem die Bezeichnung eines Elements bearbeitet wurde.
AfterSelect
Wird ausgelöst, nachdem ein Element ausgewählt wurde.
BeforeCheck
Wird ausgelöst, bevor ein Kontrollkästchen neben einem Element aktiviert wird.
BeforeCollapse
Wird ausgelöst, bevor ein Element reduziert wird.
BeforeExpand
Wird ausgelöst, bevor ein Element erweitert wird.
BeforeLabelEdit
Wird ausgelöst, bevor die Bezeichnung eines Elements bearbeitet wird.
BeforeSelect
Wird ausgelöst, bevor ein Element ausgewählt wird.
ItemDrag
Wird ausgelöst, wenn der Benutzer ein Element in die Liste zieht.
Tabelle 1.58: Eigenschaften, Methoden und Ereignisse von TreeView (Forts.)
Das TreeNode-Steuerelement repräsentiert jedes Element in einem TreeView-Steuerelement. Jedes TreeNode-Steuerelement kann Unterknoten besitzen, um so eine hierarchisch gegliederte Ansicht zu bilden. Es ist nicht von der Control-Klasse abgeleitet. Tabelle B.59 führt die Eigenschaften und Methoden dieses Steuerelements auf.
67
Bonuskapitel 1
Eigenschaft
Beschreibung
BackColor
Die Hintergrundfarbe des Knotens.
Bounds
Das umschließende Rechteck des Knotens.
Checked
Gibt an, ob der Knoten aktiviert ist.
FirstNode
Liefert den ersten untergeordneten Strukturknoten in der Auflistung.
ForeColor
Die Vordergrundfarbe des Knotens.
FullPath
Der TreeView-Pfad vom Stammknoten bis zum aktuellen Knoten.
Handle
Der Handle für den Knoten.
ImageIndex
Der Index der Grafik in der ImageList-Eigenschaft der umgebenden Strukturansicht, die für den aktuellen Knoten anzuzeigen ist.
Index
Der Index des aktuellen Knotens in der Liste.
IsEditing
Gibt an, ob sich der Knoten in einem editierbaren Zustand befindet.
IsExpanded
Gibt an, ob der Knoten erweitert ist.
IsSelected
Gibt an, ob der Knoten ausgewählt ist.
IsVisible
Gibt an, ob der Knoten aktuell sichtbar ist.
LastNode
Liefert den letzten untergeordneten Knoten in der Auflistung.
NextNode
Liefert den nächsten Knoten in der Auflistung.
NextVisibleNode
Liefert den nächsten vollständig sichtbaren Knoten in der Auflistung.
NodeFont
Die für die Textanzeige im Knoten benutzte Schriftart.
Nodes
Eine Auflistung der untergeordneten TreeNode-Objekte dieses Knotens.
Parent
Der übergeordnete Strukturknoten des aktuellen Knotens.
PrevNode
Liefert den vorhergehenden Knoten in der Auflistung.
PrevVisibleNode
Liefert die vorher vollständig sichtbaren Knoten in der Auflistung
SelectedImageIndex
Liefert den Index der Grafik in der ImageList-Eigenschaft der umgebenden Strukturansicht, die zu verwenden ist, wenn der Knoten ausgewählt wird.
Tag
Das Objekt, das Informationen über den Knoten enthält.
Tabelle 1.59: Eigenschaften und Methoden von TreeNode
68
Bonuskapitel 1
Eigenschaft
Beschreibung
Text
Der im Knoten anzuzeigende Text.
TreeView
Liefert das umgebende TreeView-Steuerelement.
Methode
Beschreibung
BeginEdit
Versucht, den Knoten in einen Zustand zu versetzen, in dem die Bearbeitung zulässig ist.
Clone
Fertigt eine Kopie der Strukturansicht und all ihrer untergeordneten Knoten an.
Collapse
Reduziert den aktuellen Knoten.
EndEdit
Stoppt den Bearbeitungsmodus, der von BeginEdit aufgerufen wurde.
EnsureVisible
Stellt sicher, dass der aktuelle Knoten sichtbar ist, wobei die Methode einen Bildlauf durch die umgebende Strukturansicht ausführt und ggf. Strukturknoten erweitert.
Expand
Erweitert den aktuellen Knoten.
ExpandAll
Erweitert alle Knoten in der Auflistung.
FromHandle
Liefert den Strukturknoten, der mit dem angegebenen TreeView-Steuerelement und dem Handle verknüpft ist.
GetNodeCount
Liefert die Anzahl von Strukturknoten, die zu diesem Knoten gehören.
Remove
Entfernt den aktuellen Knoten aus der Auflistung.
Toggle
Schaltet den Knoten entweder in den erweiterten oder den reduzierten Zustand.
Tabelle 1.59: Eigenschaften und Methoden von TreeNode (Forts.)
69
Bonuskapitel 2: Steuerelemente in ADO.NET: Eigenschaften und Methoden
2
Bonuskapitel 2
ADO.NET besteht aus zwei Teilen: dem DataSet und seinen verwandten Klassen einerseits, sowie den verwalteten Providern, die die Kommunikation mit Datenquellen ermöglichen. Dieser Anhang beschreibt die Klassen in jedem dieser beiden Teile.
2.1
Die DataSet- und verwandte Klassen
Im Folgenden finden Sie Einzelheiten zum DataSet und verwandten Elementen wie etwa DataRelation, DataTable usw.
Constraint und ConstraintCollection Die Constraint-Klasse repräsentiert eine Regel für eine Tabelle, die die bearbeitbaren Daten eingrenzt. Tabelle C.1 listet die Eigenschaften der Constraint-Klasse auf. Eigenschaft
Beschreibung
ConstraintName
Der Name dieser Bedingung.
ExtendedProperties
Holt eine Auflistung von benutzerdefinierten Eigenschaften.
Table
Liefert eine DataTable, auf die sich diese Bedingung anwenden lässt.
Tabelle 2.1: Eigenschaften der Constraint-Klasse
Tabelle C.2 listet die Eigenschaft, die Methoden und die Ereignisse der ConstraintCollection-Klasse auf. Eigenschaft
Beschreibung
Item
Holt eine Bedingung in der Auflistung, entweder mit dem Namen der Bedingung oder ihrem Index in der Auflistung.
Methoden
Beschreibung
Add
Fügt der Auflistung eine Bedingung hinzu. Diese Methode ist überladen.
AddRange
Kopiert die Elemente aus einem anderen ConstraintCollection-Objekt in das aktuelle.
Tabelle 2.2: Die Eigenschaft, die Methoden und das Ereignis der ConstraintCollection-Klasse
72
Bonuskapitel 2
Methoden
Beschreibung
CanRemove
Gibt an, ob eine Bedingung, die von Constraint festgelegt wurde, von der DataTable entfernt werden kann.
Clear
Löscht die Auflistung aller Constraint-Objekte.
Contains
Gibt an, ob die Bedingung mit dem Namen name in der Auflistung existiert.
IndexOf
Holt den Index der angegebenen Bedingung. Diese Methode ist überladen.
Remove
Entfernt die angegebene Bedingung aus der Auflistung. Diese Methode ist überladen.
RemoveAt
Entfernt die Bedingung beim angegebenen Index.
Ereignis
Beschreibung
CollectionChanged
Tritt auf, wenn die Auflistung durch Hinzufügungen oder Löschungen geändert wird. Verwendet ein CollectionChangeEventArgs-Objekt als Ereignisparameter. Dieses Objekt enthält die folgenden Eigenschaften: Action: Liefert einen Wert (Hinzufügen, Entfernen, Aktualisieren), der angibt, wie sich die Auflistung verändert hat. Element: Liefert die Instanz der Auflistung, die sich verändert hat.
Tabelle 2.2: Die Eigenschaft, die Methoden und das Ereignis der ConstraintCollection-Klasse (Forts.)
DataColumn und DataColumnCollection Eine DataColumn stellt eine Spalte von Informationen in einer DataTable dar. Tabelle C.3 listet die Eigenschaften und die Methode der DataColumn-Klasse auf. Eigenschaft
Beschreibung
AllowDBNull
Gibt an, ob NULL-Werte in dieser Spalte erlaubt sind.
AutoIncrement
Gibt an, ob der Wert der Spalte automatisch mit dem Hinzufügen einer neuen Zeile wächst.
AutoIncrementSeed
Der Ausgangswert für AutoIncrement.
AutoIncrementStep
Der Zuwachswert, den AutoIncrement benutzt.
Caption
Die Überschrift für diese Spalte.
Tabelle 2.3: Eigenschaften und Methode der DataColumn-Klasse
73
Bonuskapitel 2
Eigenschaft
Beschreibung
ColumnMapping
Liefert ein MappingType-Objekt, das angibt, wie die Spalte abgebildet wird, wenn sie in XML geschrieben wird.
ColumnName
Der Name der Spalte.
DataType
Der Typ von Daten, der in dieser Spalte gespeichert wird.
DefaultValue
Der Standardwert für diese Spalte, wenn neue Zeilen erzeugt werden.
Expression
Ein Zeichenfolgenausdruck, der als Zeilenfilter sowie zur Berechnung des Spaltenwertes benutzt wird, oder der eine aggregierte Spalte erzeugen kann.
ExtendedProperties
Liefert eine PropertyCollection-Auflistung von benutzerdefinierten Benutzerinformationen.
MaxLength
Die maximale Länge einer Textspalte.
Namespace
Der XML-Namensraum, der die in dieser Spalte benutzten Elemente enthält.
Ordinal
Die Position dieser Spalte in der DataColumnCollection-Auflistung.
Prefix
Das Präfix, das für diese Spalte benutzt wird, wenn sie als XML dargestellt wird.
ReadOnly
Gibt an, ob die Spalte Änderungen zulässt.
Table
Liefert die DataTable, zu der diese Spalte gehört.
Unique
Gibt an, ob jeder Wert in dieser Spalte eindeutig sein muss.
Methode
Beschreibung
ToString
Liefert den Ausdruck dieser Spalte, sofern er existiert.
Tabelle 2.3: Eigenschaften und Methode der DataColumn-Klasse (Forts.)
Tabelle C.4 listet die Eigenschaft, die Methoden und das Ereignis der DataColumnCollection-Klasse auf. Eigenschaft
Beschreibung
Item
Holt eine DataColumn in der Auflistung entweder mit dem Namen der Spalte oder mit dem Index in der Auflistung.
Tabelle 2.4: Eigenschaft, Methoden und Ereignis der DataColumnCollection-Klasse
74
Bonuskapitel 2
Methoden
Beschreibung
Add
Fügt der Auflistung eine Spalte hinzu. Diese Methode ist überladen.
AddRange
Fügt der Auflistung ein Array von DataColumn-Objekten hinzu.
CanRemove
Gibt an, ob eine Spalte, die durch Column angegeben ist, aus der Auflistung entfernt werden kann.
Clear
Löscht alle DataColumn-Objekte in der Auflistung.
Contains
Gibt an, ob die DataColumn mit dem Namen name in der Auflistung vorhanden ist.
IndexOf
Holt den Index der angegebenen Spalte. Diese Methode ist überladen.
Remove
Entfernt die angegebene Spalte aus der Auflistung. Diese Methode ist überladen.
RemoveAt
Entfernt die DataColumn beim angegebenen Index.
Ereignis
Beschreibung
CollectionChanged
Tritt auf, wenn die Auflistung durch Hinzufügungen oder Löschungen verändert wurde. Verwendet ein CollectionChangeEventArgs-Objekt als Ereignisparameter. Dieses Objekt enthält die folgenden Eigenschaften: Action: Liefert einen Wert (Add, Remove, Refresh), der angibt, inwiefern sich die Auflistung verändert hat. Element: Liefert die Instanz der Auflistung, die sich verändert hat.
Tabelle 2.4: Eigenschaft, Methoden und Ereignis der DataColumnCollection-Klasse (Forts.)
2.2
DataRelation und DataRelationCollection
Eine DataRelation-Klasse stellt die Beziehungen zwischen mehreren Tabellen im DataSet dar. Tabelle C.5 listet die Eigenschaften der DataRelation-Klasse auf. Eigenschaft
Beschreibung
ChildColumns
Liefert ein Array von DataColumn-Objekten, die die untergeordneten Spalten dieser Beziehung darstellen.
ChildKeyConstraint
Ein ForeignKeyConstraint-Objekt für diese Beziehung.
Tabelle 2.5: Eigenschaften der DataRelation-Klasse
75
Bonuskapitel 2
Eigenschaft
Beschreibung
ChildTable
Liefert eine DataTable, die die untergeordnete Tabelle dieser Beziehung darstellt.
DataSet
Liefert das DataSet, zu dem diese Beziehung gehört.
ExtendedProperties
Liefert eine PropertyCollection von benutzerdefinierten Benutzerinformationen.
Nested
Gibt an, ob Beziehungen verschachtelt sind.
ParentColumns
Ein Array von DataColumn-Objekten, die die übergeordneten Spalten dieser Beziehung darstellen.
ParentKeyConstraint
Ein UniqueConstraint-Objekt für diese Beziehung.
ParentTable
Liefert die DataTable, die die übergeordnete Tabelle dieser Beziehung darstellt.
RelationName
Der Name dieser Beziehung.
Tabelle 2.5: Eigenschaften der DataRelation-Klasse (Forts.)
Tabelle C.6 listet die Eigenschaft, die Methoden und das Ereignis der DataRelationCollection-Klasse auf. Eigenschaft
Beschreibung
Item
Holt eine DataRelation aus der Auflistung, entweder mit dem Namen der Beziehung oder mit ihrem Index in der Auflistung.
Methode
Beschreibung
Add
Fügt der Auflistung eine Beziehung hinzu. Diese Methode ist überladen.
AddRange
Fügt ein Array von DataRelation-Objekten zur Auflistung hinzu.
CanRemove
Prüft, ob sich die angegebene DataRelation aus der Auflistung entfernen lässt.
Clear
Löscht alle DataRelation-Objekte aus der Auflistung.
Contains
Gibt an, ob die DataRelation mit dem Namen name in der Auflistung existiert.
IndexOf
Liefert den Index der angegebenen DataRelation.
Tabelle 2.6: Eigenschaft, Methoden und Ereignis der DataRelationCollection-Klasse
76
Bonuskapitel 2
Methode
Beschreibung
Remove
Entfernt die angegebene Beziehung aus der Auflistung. Diese Methode ist überladen.
RemoveAt
Entfernt die DataRelation am angegebenen Index.
Ereignis
Beschreibung
CollectionChanged
Tritt auf, wenn die Auflistung durch Hinzufügungen oder Löschungen verändert wird. Benutzt ein CollectionChangeEventArgs-Objekt als Ereignisparameter. Dieses Objekt enthält die folgenden Eigenschaften: Action: Liefert einen Wert (Add, Remove, Refresh), der angibt, inwiefern die Auflistung verändert wurde. Element: Liefert die Instanz der Auflistung, die sich verändert hat.
Tabelle 2.6: Eigenschaft, Methoden und Ereignis der DataRelationCollection-Klasse (Forts.)
2.3
DataRow und DataRowCollection
Eine DataRow repräsentiert eine Zeile von Informationen in einer DataTable, das heißt, einen einzelnen Datensatz. Tabelle C.7 listet die Eigenschaften und Methoden der DataRow-Klasse auf. Eigenschaft
Beschreibung
HasErrors
Gibt an, ob die Daten in der Zeile Fehler enthalten.
Item
Legt die in der angegebenen Spalte enthaltenen Daten fest. Die Methode ist überladen.
ItemArray
Legt mit Hilfe eines Arrays die in der ganzen Zeile enthaltenen Daten fest.
RowError
Die benutzerdefinierte Fehlerbeschreibung für die Zeile.
RowState
Der Status der Zeile. Kann Detached, Unchanged, New, Deleted oder Modified sein.
Table
Liefert die DataTable, zu der diese Zeile gehört.
Methode
Beschreibung
AcceptChanges
Speichert alle an der Zeile vorgenommenen Änderungen.
BeginEdit
Beginnt eine Bearbeitung der Zeile.
Tabelle 2.7: Eigenschaften und Methoden der DataRow-Klasse
77
Bonuskapitel 2
Methode
Beschreibung
CancelEdit
Bricht die Bearbeitung ab und macht alle Änderungen rückgängig.
ClearErrors
Löscht alle Fehler in der Zeile.
Delete
Löscht die Zeile.
EndEdit
Beendet die Bearbeitung der Zeile.
GetChildRows
Liefert ein Array von DataRow-Objekten, die die untergeordneten Zeilen dieser Zeile darstellen, wenn die angegebene DataRelation benutzt wird.
GetColumnError
Holt die Fehler für eine angegebene Spalte in der Zeile. Diese Methode ist überladen.
GetColumnsInError
Holt ein Array von DataColumn-Objekten, die Fehler aufweisen.
GetParentRow
Liefert eine DataRow, die die übergeordnete Zeile dieser Zeile darstellt. Diese Methode ist überladen.
GetParentRows
Liefert ein Array von DataRow-Objekten, die mit der angegebenen DataRelation die übergeordnete Zeilen dieser Zeile repräsentieren. Diese Methode ist überladen.
HasVersion
Gibt an, ob eine bestimmte Version dieser Zeile existiert.
IsNull
Gibt an, ob die angegebene Spalte in der Zeile einen NULL-Wert enthält. Diese Methode ist überladen.
RejectChanges
Macht alle Änderungen an der Zeile rückgängig.
SetColumnError
Legt die Fehlerbeschreibung für die Spalte fest. Diese Methode ist überladen.
SetParentRow
Legt die übergeordnete Zeile einer angegebenen untergeordneten Zeile fest. Diese Methode ist überladen.
Tabelle 2.7: Eigenschaften und Methoden der DataRow-Klasse(Forts.)
Tabelle C.8 listet die Eigenschaft und die Methoden der DataRowCollection-Klasse auf. Eigenschaft
Beschreibung
Item
Holt eine DataRow aus der Auflistung, entweder mit dem Namen der Zeile oder mit ihrem Index in der Auflistung.
Tabelle 2.8: Eigenschaft und Methoden der DataRowCollection-Klasse
78
Bonuskapitel 2
Methode
Beschreibung
Add
Fügt eine Zeile in die Auflistung ein. Diese Methode ist überladen.
Clear
Löscht alle DataRow-Objekte in der Auflistung.
Contains
Gibt an, ob die DataRow mit dem Namen name in der Auflistung existiert.
Find
Holt eine angegebene DataRow. Diese Methode ist überladen.
InsertAt
Fügt eine neue DataRow an der angegebenen Position ein.
Remove
Entfernt die angegebene Zeile aus der Auflistung. Diese Methode ist überladen.
RemoveAt
Entfernt die DataRow am angegebenen Index.
Tabelle 2.8: Eigenschaft und Methoden der DataRowCollection-Klasse
DataSet Tabelle C.9 listet die Eigenschaften, Methoden und das Ereignis der DataSet-Klasse auf, welches eine unverbundene Datenquelle repräsentiert. Eigenschaft
Beschreibung
CaseSensitive
Gibt an, ob Zeichenfolgenvergleiche in einer DataTable auf Groß-/Kleinschreibung achten.
DataSetName
Der Name dieses DataSets.
DefaultViewManager
Liefert einen DataViewManager, der eine benutzerdefinierte Sicht der Daten im DataSet enthält.
EnforceConstraints
Gibt an, ob bei der Datenaktualisierung Bedingungsregeln befolgt werden.
ExtendedProperties
Ein PropertyCollection-Objekt, das benutzerdefinierte Benutzerinformationen enthält.
HasErrors
Gibt an, ob die Daten in einer der Zeilen dieses DataSets Fehler enthalten.
Locale
Die Positionsinformation, die für den Zeichenfolgenvergleich benutzt wird. Liefert ein CultureInfo-Objekt.
Namespace
Gibt den Namensraum des DataSets an.
Tabelle 2.9: Eigenschaften, Methoden und Ereignis der DataSet-Klasse
79
Bonuskapitel 2
Eigenschaft
Beschreibung
Prefix
Ein XML-Alias für den Namensraum des DataSets.
Relations
Ein DataRelationCollection-Objekt, das alle Beziehungen zwischen Tabellen im DataSet repräsentiert.
Site
Liefert eine ISite-Schnittstelle für das DataSet (wird für das Anbinden von Komponenten an Container verwendet).
Tables
Ein DataTableCollection-Objekt, das alle Tabellen in dem DataSet repräsentiert
Methode
Beschreibung
AcceptChanges
Speichert alle am DataSet vorgenommenen Änderungen.
Clear
Entfernt alle Zeilen in allen Tabellen im DataSet.
Clone
Erzeugt ein DataSet, das mit dem aktuellen DataSet identisch ist, aber keine Daten enthält.
Copy
Erzeugt ein DataSet, das mit dem aktuellen DataSet identisch ist, aber diesmal Daten enthält.
GetChanges
Erzeugt ein DataSet, das nur die Daten enthält, die verändert wurden.
GetXml
Liefert die Daten im DataSet im XML-Format.
GetXmlSchema
Liefert das XML-Schema für die Daten im DataSet.
HasChanges
Gibt an, ob die Daten im DataSet geändert wurden.
InferXmlSchema
Erstellt die Datenstruktur aus einer XML-Datenquelle. Diese Funktion ist überladen.
Merge
Führt das angegebene DataSet mit einem anderen zusammen.
ReadXml
Fügt Daten und Schemainformationen aus einer XML-Datei in ein DataSet ein.
ReadXmlSchema
Erstellt die Datenstruktur aus einem XML-Schema. Diese Funktion ist überladen.
RejectChanges
Macht alle Änderungen an diesem DataSet rückgängig.
Reset
Setzt das DataSet auf seine Standardeigenschaften zurück.
Tabelle 2.9: Eigenschaften, Methoden und Ereignis der DataSet-Klasse(Forts.)
80
Bonuskapitel 2
Methode
Beschreibung
WriteXml
Schreibt den Inhalt des DataSets im XML-Format. Diese Funktion ist überladen.
WriteXmlSchema
Schreibt die Struktur des DataSets im XML-Format. Diese Funktion ist überladen.
Ereignis
Beschreibung
MergeFailed
Tritt auf, wenn eine Ziel- und Quell-DataRow den gleichen Primärschlüsselwert haben und EnforceConstraints true ist.
Tabelle 2.9: Eigenschaften, Methoden und Ereignis der DataSet-Klasse(Forts.)
DataTable und DataTableCollection Eine DataTable repräsentiert eine Tabelle von Informationen in einem DataSet. Tabelle C.10 listet die Eigenschaften, Methoden und Ereignisse der DataTable-Klasse auf. Eigenschaft
Beschreibung
CaseSensitive
Gibt an, ob Zeichenfolgenvergleiche in der Tabelle auf Groß-/Kleinschreibung achten.
ChildRelations
Liefert eine DataRelationCollection der untergeordneten Beziehungen dieser Tabelle.
Columns
Liefert ein DataColumnCollection-Objekt, das die Spalte in dieser Tabelle repräsentiert.
Constraints
Liefert ein DataRelationCollection-Objekt, das die Datenbeziehungen in dieser Tabelle repräsentiert.
DataSet
Liefert das DataSet, zu dem diese Tabelle gehört.
DefaultView
Liefert eine DataView, die eine benutzerdefinierte Sicht der Daten in dieser Tabelle darstellt.
DisplayExpression
Ein Zeichenfolgenausdruck, der einen Wert liefert, der angibt, wie diese Tabelle in der Benutzeroberfläche angezeigt werden soll.
ExtendedProperties
Liefert eine PropertyCollection von benutzerdefinierten Benutzerinformationen.
HasErrors
Gibt an, ob es Fehler in einer der Zeilen dieser Tabelle gibt.
Tabelle 2.10: Eigenschaften, Methoden und Ereignisse der DataTable-Klasse
81
Bonuskapitel 2
Eigenschaft
Beschreibung
Locale
Ein CultureInfo-Objekt, das man dazu einsetzt festzulegen, wie Zeichenfolgen miteinander verglichen werden.
MinimumCapacity
Die anfängliche Größe dieser Tabelle.
Namespace
Der XML-Namensraum, der die Elemente in dieser Tabelle enthält.
ParentRelations
Eine DataRelationCollection der übergeordneten Beziehungen dieser Tabelle.
Prefix
Das Präfix, das man für diese Tabelle benutzt, wenn sie als XML dargestellt wird.
PrimaryKey
Ein Array von DataColumn-Objekten, die als Primärschlüssel für die Tabelle dienen.
Rows
Ein DataRowCollection-Objekt, das die Zeilen darstellt, die zu dieser Tabelle gehören.
Site
Liefert eine ISite-Schnittstelle für die DataTable (wird benutzt, um Komponenten an Container zu binden).
TableName
Der Name der Tabelle.
Methode
Beschreibung
AcceptChanges
Speichert alle an der Tabelle vorgenommenen Änderungen.
BeginInit
Beginnt mit der Initialisierung dieser Tabelle.
BeginLoadData
Beginnt mit dem Datenladevorgang.
Clear
Löscht alle Daten aus der Tabelle.
Clone
Fertigt eine Kopie der DataTable-Struktur an, einschließlich all ihrer Beziehungen.
Compute
Berechnet den Ausdruck, der im ersten Parameter in den Zeilen angegeben ist, die den angegebenen Filter durchlaufen.
Copy
Kopiert sowohl die Struktur als auch die Daten, die in der DataTable enthalten sind.
EndInit
Beendet den Initialisierungsvorgang.
EndLoadData
Beendet den Datenladevorgang.
Tabelle 2.10: Eigenschaften, Methoden und Ereignisse der DataTable-Klasse(Forts.)
82
Bonuskapitel 2
Methode
Beschreibung
GetChanges
Holt eine Kopie der DataTable, die diejenigen Änderungen enthält, die seit ihrer Erstellung angefallen sind oder seit AcceptChanges aufgerufen wurde.
GetErrors
Ein Array von DataRow-Objekten, die Fehler enthalten.
ImportRow
Kopiert ein DataRow-Objekt in die DataTable.
LoadDataRow
Sucht und aktualisiert eine DataRow mit den angegebenen Werten. Wird eine Zeile nicht gefunden, wird eine neue erzeugt.
NewRow
Liefert eine leere DataRow mit dem gleichen Schema wie die Tabelle.
RejectChanges
Macht alle Änderungen an der Tabelle rückgängig, die vorgenommen wurden, seit sie erstmals geladen wurde oder seit AcceptChanges zuletzt aufgerufen wurde.
Reset
Setzt die DataTable auf ihre Standardeigenschaften zurück.
Select
Liefert ein Array von DataRow-Objekten. Diese Methode ist überladen.
ToString
Liefert TableName und DisplayExpression für diese Tabelle.
Ereignis
Beschreibung
ColumnChanged
Tritt auf, wenn eine Spalte geändert wurde. Verwendet ein DataColumnChangedEventArgs-Objekt als Ereignisparameter. Dieses Objekt enthält die folgenden Eigenschaften: Column: Die Spalte wurde verändert. ProposedValue: Der Wert, in den die Spalte zu ändern ist.
Row: Die zu ändernde DataRow. ColumnChanging
Tritt auf, wenn Änderungen an dieser Spalte gespeichert werden sollen. Verwendet ein DataColumnChangedEventArgs-Objekt.
RowChanged
Tritt auf, wenn eine Zeile verändert wurde. Verwendet ein DataRowChangedEventArgs-Objekt als Ereignisparameter. Dieses Objekt enthält die folgenden Eigenschaften: Action: Die Aktion, die an dieser DataRow ausgeführt wurde. Row: Die zu ändernde DataRow.
RowChanging
Tritt auf, wenn Änderungen an dieser Spalte gespeichert werden sollen. Verwendet ein DataRowChangedEventArgs-Objekt.
Tabelle 2.10: Eigenschaften, Methoden und Ereignisse der DataTable-Klasse(Forts.)
83
Bonuskapitel 2
Methode RowDeleted
Beschreibung Tritt auf, nachdem eine Zeile gelöscht wurde. Verwendet ein DataRowChangedEventArgs-Objekt.
RowDeleting
Tritt auf, bevor eine Zeile gelöscht wird. Verwendet ein DataRowChangedEventArgs-Objekt.
Tabelle 2.10: Eigenschaften, Methoden und Ereignisse der DataTable-Klasse(Forts.)
Tabelle C.11 listet die Eigenschaft, Methoden und Ereignisse der DataTableCollectionKlasse auf. Eigenschaft
Beschreibung
Item
Holt eine DataTable aus der Auflistung, entweder mit dem Namen der Tabelle oder mit ihrem Index in der Auflistung.
Methoden
Beschreibung
Add
Fügt der Auflistung eine Tabelle hinzu. Diese Methode ist überladen.
AddRange
Fügt der Auflistung ein Array von DataTable-Objekten hinzu.
CanRemove
Gibt an, ob die angegebene Tabelle aus der Auflistung entfernt werden kann.
Clear
Löscht alle DataTable-Objekte aus der Auflistung.
Contains
Gibt an, ob die DataTable mit dem angegebenen Namen in der Auflistung existiert.
IndexOf
Holt den Index der angegebenen Tabelle. Diese Methode ist überladen.
Remove
Entfernt die angegebene Tabelle aus der Auflistung. Diese Methode ist überladen.
RemoveAt
Entfernt die DataTable am angegebenen Index.
Ereignis
Beschreibung
CollectionChanged
Tritt auf, wenn die Auflistung durch Hinzufügen oder Entfernen von Elementen verändert wird. Verwendet ein CollectionChangeEventArgs-Objekt als Ereignisparameter. Dieses Objekt enthält die folgenden Eigenschaften: Action: Liefert einen Wert (Add, Remove, Refresh), der angibt, wie sich die Auflistung verändert hat. Element: Liefert die Instanz der veränderten Auflistung.
Tabelle 2.11: Eigenschaft, Methoden und Ereignisse der DataTableCollection-Klasse
84
Bonuskapitel 2
Ereignis
Beschreibung
CollectionChanging
Tritt auf, bevor die Auflistung verändert wird. Verwendet ein CollectionChangeEventArgs-Objekt.
Tabelle 2.11: Eigenschaft, Methoden und Ereignisse der DataTableCollection-Klasse(Forts.)
DataView Eine DataView-Klasse repräsentiert eine benutzerdefinierte Sicht auf Daten in einem DataSet. Tabelle C.12 listet die Eigenschaften, die Methoden und das Ereignis der DataViewKlasse auf. Eigenschaft
Beschreibung
AllowDelete
Gibt an, ob Löschvorgänge in dieser Ansicht erlaubt sind.
AllowEdit
Gibt an, ob Editiervorgänge in dieser Ansicht erlaubt sind.
AllowNew
Gibt an, ob neue Zeilen in dieser Ansicht hinzugefügt werden können.
ApplyDefaultSort
Gibt an, ob die Standardsortierfolge benutzt werden soll.
Count
Liefert die Anzahl der Datensätze in der DataView.
DataViewManager
Die DataView, die diese Ansicht erzeugt hat (ein Zeiger auf die DataSetView, die das korrespondierende DataSet besitzt).
Item
Holt eine angegebene Zeile von Daten aus einer Tabelle.
RowFilter
Ein Ausdruck, der als Filter dafür benutzt wird, welche Zeilen der DataView hinzugefügt werden.
RowStateFilter
Legt fest, welche Zeilenversion der DataView hinzugefügt wird. Kann None, Unchanged, New, Deleted, ModifiedCurrent, ModifiedOriginal, OriginalRows oder CurrentRows (Standard) sein.
Sort
Die Spalten, nach denen sortiert wird.
Table
Die Quell-DataTable, aus der Daten herauszuziehen sind.
Methode
Beschreibung
AddNew
Fügt der DataView eine neue Zeile hinzu.
BeginInit
Beginnt mit der Initialisierung dieser DataView.
Tabelle 2.12: Eigenschaften, Methoden und Ereignis der DataView-Klasse
85
Bonuskapitel 2
Methode
Beschreibung
CopyTo
Kopiert Elemente in ein Array.
Delete
Löscht eine Zeile am angegebenen Index.
EndInit
Beendet den Initialisierungsprozess.
Find
Sucht eine angegebene Zeile in der DataView. Diese Methode ist überladen.
FindRows
Liefert ein Array von DataRowView-Objekten, die den angegebenen Kriterien entsprechen.
GetEnumerator
Liefert einen IEnumerator, der sich dazu verwenden lässt, durch diese DataView zu iterieren.
Ereignis
Beschreibung
ListChanged
Tritt auf, wenn sich die Liste, die von der DataView verwaltet wird, ändert. Verwendet ein ListChangedEventArgs-Objekt als Ereignisparameter. Dieses Objekt enthält die folgenden Eigenschaften: ListChangedType: Die Art und Weise, wie sich die Liste geändert hat. NewIndex: Der neue Index des geänderten Elements. OldIndex: Der alte Index des geänderten Elements.
Tabelle 2.12: Eigenschaften, Methoden und Ereignis der DataView-Klasse(Forts.)
2.4
Verwaltete Provider
Mit verwalteten Providern kann ADO.NET mit jedem Typ von OLE DB-konformen Datenquellen interagieren. Diese Provider werden dazu eingesetzt, Daten in das DataSet und dessen verwandte Klassen zu bewegen, und lassen sich zusätzlich unabhängig voneinander einsetzen, um Daten zu bearbeiten. Es gibt zweierlei verwaltete Provider: OleDB und Sql. Der erste befasst sich mit allen OLE DB-konformen Datenspeichern, wohingegen der zweite nur mit SQL Server zusammenarbeitet. In fast allen Fällen entsprechen die Klassen des einen Providers genau den Klassen des anderen; der einzige Unterschied besteht in der Verwendung des Präfixes: OleDb und Sql. So haben z.B. beide Provider eine Klasse, die raschen Zugriff auf Daten gewährt, die jeweils OleDbDataReader bzw. SqlDataReader heißt. Wegen der Ähnlichkeit dieser beiden Provider wird hier nur der verwaltete OleDb-Provider behandelt. Besteht ein Unterschied zur SQL-Version, so wird dies erwähnt.
86
Bonuskapitel 2
OleDbCommand Die OleDbCommand-Klasse repräsentiert ein SQL-Statement, das für eine Datenquelle zu erzeugen ist. Tabelle C.13 listet die Eigenschaften und Methoden dieser Klasse auf. Eigenschaft
Beschreibung
CommandText
Das auszuführende SQL-Statement.
CommandTimeout
Das Zeit-Limit, innerhalb dessen der Befehl vor seiner Beendigung ausgeführt sein muss.
CommandType
Legt fest, wie die CommandText-Eigenschaft interpretiert wird. Kann StoredProcedure, TableDirect oder Text (Standard) sein.
Connection
Legt eine OleDbConnection fest, die von diesem Objekt benutzt wird.
DesignTimeVisible
Gibt an, ob das Befehlsobjekt in einem benutzerdefinierten Windows FormsDesigner sichtbar sein soll.
Parameters
Holt ein OleDbParameterCollection-Objekt, das die Parameter für den Einsatz mit diesem Befehl repräsentiert.
Transaction
Die OleDbTransaction, die der Befehl benutzt.
UpdatedRowSource
Die Anzahl von Datensätzen, die von dem Befehl betroffen sind. Normalerweise 1, falls der Befehl erfolgreich ausgeführt wird, und kleiner 1, falls nicht.
Methode
Beschreibung
Cancel
Bricht die Ausführung des Befehls ab.
CreateParameter
Erzeugt einen OleDbParameter zur Verwendung mit diesem Befehl.
ExecuteNonQuery
Führt ein SQL-Statement aus, das keinerlei Daten liefert.
ExecuteReader
Liefert einen OleDbDataReader, der mit den Daten gefüllt ist, die der Befehl geliefert hat.
ExecuteScalar
Führt die Abfrage aus und liefert Werte für die erste Spalte und erste Zeile der gelieferten Ergebnisse.
Prepare
Erzeugt eine kompilierte Version des Befehls.
ResetCommandTimeout
Setzt die CommandTimeout-Eigenschaft auf den Standardwert zurück.
Tabelle 2.13: Eigenschaften und Methoden der OleDbCommand-Klasse
87
Bonuskapitel 2
OleDBCommandBuilder Die OleDbCommandBuilder-Klasse stellt eine einfache Möglichkeit dar, Befehle für eine bestimmte Datenquelle zu erzeugen. Tabelle C.14 listet die Eigenschaften und Methoden dieser Klasse auf. Eigenschaft
Beschreibung
DataAdapter
Der Name eines OleDbDataAdapters, für den die Befehle erzeugt wurden.
QuotePrefix
Legt ein Präfix fest, das zu benutzen ist, wenn man Datenquellenobjektnamen festlegt (wie etwa »tbl« für Tabellen, »sp« für gespeicherte Prozeduren (Stored Procedures)und so weiter).
QuoteSuffix
Legt ein Suffix fest, das zu benutzen ist, wenn man Objektnamen von Datenquellen festlegt.
Methode
Beschreibung
DeriveParameters
Füllt die Parameters-Auflistung von OleDbCommand mit in der gespeicherten Prozedur angegebenen Werten.
GetDeleteCommand
Holt das automatisch generierte SQL-Statement, um Zeilen aus der Datenquelle zu löschen.
GetInsertCommand
Holt das automatisch generierte SQL-Statement, um Zeilen in die Datenquelle einzufügen.
GetUpdateCommand
Holt das automatisch generierte SQL-Statement, um Zeilen in der Datenquelle zu aktualisieren.
RefreshSchema
Holt das Schema der Datenquelle.
Tabelle 2.14: Eigenschaften und Methoden der OleDbCommandBuilder-Klasse
OleDbConnection Die OleDbConnection-Klasse repräsentiert eine Verbindung zu einer Datenquelle. Tabelle C.15 listet die Eigenschaften, Methoden und Ereignisse dieser Klasse auf. Eigenschaft
Beschreibung
ConnectionString
Die Zeichenfolge, mit der eine Datenbank geöffnet wird.
Tabelle 2.15: Eigenschaften, Methoden und Ereignisse der OleDbConnection-Klasse
88
Bonuskapitel 2
Eigenschaft
Beschreibung
ConnectionTimeout
Das Zeit-Limit für das Zustandekommen einer Verbindung zur Datenbank, nach dessen Überschreiten eine Fehlermeldung erzeugt wird.
Database
Der Name der Datenbank, der nach dem Zustandekommen einer Verbindung zu verwenden ist.
DataSource
Der Name der Datenbank, zu der eine Verbindung aufzubauen ist.
Provider
Der Name des Datenbank-Providers.
ServerVersion
Eine Zeichenfolge, die die Version des Servers enthält, mit dem der Client verbunden ist.
State
Der aktuelle Zustand der Verbindung.
Methode
Beschreibung
BeginTransaction
Beginnt mit einer Datenbanktransaktion. Diese Methode ist überladen.
ChangeDatabase
Ändert die aktuelle Datenbank auf den angegebenen Wert.
Close
Schließt die Datenbankverbindung.
CreateCommand
Liefert ein OleDbCommand-Objekt, um damit Befehle gegen die Datenbank auszuführen.
GetOleDbSchemaTable
Liefert Schemainformationen aus einer angegebenen Datenquelle durch eine GUID. (Diese Methode existiert nicht in der entsprechenden SqlConnectionKlasse.)
Open
Versucht, eine Verbindung zur Datenbank zu öffnen.
ReleaseObjectPool
Gibt an, dass das Pooling von OleDbConnection-Objekten gelöscht werden kann, sobald der letzte zugrunde liegende OLE DB-Provider freigegeben worden ist.
Ereignis
Beschreibung
InfoMessage
Tritt auf, wenn der Provider eine Mitteilung schickt. Verwendet ein OleDbInfoMessageEventArgs-Objekt als Ereignisparameter. Dieses Objekt enthält die folgenden Eigenschaften: ErrorCode: Ein HRESULT-Wert, der die Standardfehlermeldung angibt. Error: Eine OleDbErrorCollection von Alarmmeldungen, die der Provider schickt. Message: Der vollständige Text der Fehlermeldung aus dem Provider. Source: Der Name des Objekts, das den Fehler erzeugt hat.
Tabelle 2.15: Eigenschaften, Methoden und Ereignisse der OleDbConnection-Klasse(Forts.)
89
Bonuskapitel 2
Ereignis
Beschreibung
StateChange
Tritt auf, wenn sich der Zustand der Verbindung ändert. Verwendet ein StateChangeEventArgs–Objekt als Ereignisparameter. Dieses Objekt enthält die folgenden Eigenschaften: CurrentStat: Der neue Zustand der Verbindung. OriginalStat: Der ursprüngliche Zustand der Verbindung.
Tabelle 2.15: Eigenschaften, Methoden und Ereignisse der OleDbConnection-Klasse(Forts.)
OleDbDataAdapter Die OleDbDataAdapter-Klasse repräsentiert eine Menge datenbezogener Befehle sowie eine Datenbankverbindung, die dazu verwendet werden können, ein DataSet zu füllen. Tabelle C.16 listet die Eigenschaften, Methoden und Ereignisse dieser Klasse auf. Eigenschaft
Beschreibung
DeleteCommand
Liefert ein OleDbCommand-Objekt, das ein SQL-Statement für die Löschung von Daten aus dem DataSet enthält.
InsertCommand
Liefert ein OleDbCommand-Objekt, das ein SQL-Statement für das Einfügen von Daten in das DataSet enthält.
SelectCommand
Liefert ein OleDbCommand-Objekt, das ein SQL-Statement für die Auswahl von Daten aus dem DataSet enthält.
UpdateCommand
Liefert ein OleDbCommand-Objekt, das ein SQL-Statement für die Aktualisierung von Daten im DataSet enthält.
Methode
Beschreibung
Fill
Fügt einem DataSet Zeilen hinzu oder ändert sie, damit es der Datenquelle entspricht. Diese Methode ist überladen.
FillSchema
Fügt eine DataTable dem angegebenen DataSet hinzu und konfiguriert das Schema der Tabelle. Diese Methode ist überladen.
GetFillParameters
Liefert ein Array von IDataParameter-Objekten, die mit Hilfe des SELECTBefehls eingesetzt werden.
Update
Aktualisiert die Datenquelle mit den Informationen im DataSet mit Hilfe der Eigenschaften Delete, Insert und UpdateCommand. Diese Methode ist überladen.
Tabelle 2.16: Eigenschaften, Methoden und Ereignisse der OleDbDataAdapter-Klasse
90
Bonuskapitel 2
Ereignis
Beschreibung
FillError
Tritt auf, wenn ein Fehler während einer Fill-Operation geliefert wird. Verwendet ein FillErrorEventArgs-Objekt als Ereignisparameter. Dieses Objekt enthält die folgenden Eigenschaften: Continue: Gibt an, ob die Operation fortzusetzen ist. DataTable: Die DataTable, die beim Auftreten des Fehlers gerade aktualisiert wurde. Errors: Liefert ein Exception-Objekt, das die zu behandelnden Fehler repräsentiert. Values: Liefert ein Objekt, das die Werte für diejenige Zeile repräsentiert, die beim Auftreten des Fehlers gerade aktualisiert wurde.
RowUpdated
Tritt auf, nachdem ein Update-Befehl ausgeführt worden ist. Verwendet ein OleDbRowUpdatedEventArgs-Objekt als Ereignisparameter. Dieses Objekt enthält die folgenden Eigenschaften: Command: Liefert den OleDbCommand, der ausgeführt wird, sobald Update aufgerufen wurde. Errors: Liefert ein Exception-Objekt, das die aufgetretenen Fehler repräsentiert. RecordsAffected: Die Anzahl der betroffenen Datensätze. Row: Holt die DataRow, die Update verwendet. StatementType: Der Typ des SQL-Statements, das ausgeführt wird. Status: Ein UpdateStatus-Objekt, das den Status des Befehls repräsentiert. TableMapping: Holt die DataTableMapping, die mit dem Update-Befehl geschickt wird.
RowUpdating
Tritt auf, bevor ein Update-Befehl ausgeführt worden ist. Verwendet ein OleDbRowUpdatingEventArgs-Objekt als Ereignisparameter. Dieses Objekt enthält die folgenden Eigenschaften: Command: Liefert den OleDbCommand, der beim Aufruf von Update auszuführen ist. Errors: Liefert ein Exception-Objekt, das die aufgetretenen Fehler repräsentiert. Row: Holt die DataRow, die Update verwendet. StatementType: Der Typ des SQL-Statements, das auszuführen ist. Status: Ein UpdateStatus-Objekt, das den Status des Befehls repräsentiert. TableMapping: Holt die DataTableMapping, die mit dem Update-Befehl geschickt wird.
Tabelle 2.16: Eigenschaften, Methoden und Ereignisse der OleDbDataAdapter-Klasse(Forts.)
91
Bonuskapitel 2
OleDbdataReader Die OleDbDataReader-Klasse repräsentiert eine rasche Strom-basierte Methode für den Datenzugriff auf eine Datenquelle. Sie gleicht einem DataSet, bietet aber geringere Funktionalität bei höherer Performanz. Tabelle C.17 listet die Eigenschaften und Methoden dieser Klasse auf. Eigenschaft
Beschreibung
Depth
Die Tiefe des Lesers.
FieldCount
Die Anzahl der Felder im aktuellen Datensatz.
IsClosed
Gibt an, ob der Datenleser geschlossen wurde.
Item
Der Wert der angegebenen Spalte in seinem nativen Format. Diese Methode ist überladen.
RecordsAffected
Die Anzahl der vom Befehl betroffenen Datensätze. Normalerweise ist diese Anzahl 1, wenn der Befehl erfolgreich ausgeführt wurde, und kleiner 1, falls nicht.
Methode
Beschreibung
Close
Schließt das OleDbDataReader-Objekt.
GetBoolean
Liefert den Wert in der angegebenen Spalte als booleschen Wert.
GetByte
Liefert den Wert in der angegebenen Spalte als Byte-Wert.
GetBytes
Liefert den Wert in der angegebenen Spalte als Byte-Array.
GetChar
Liefert den Wert in der angegebenen Spalte als Char-Wert.
GetChars
Liefert den Wert in der angegebenen Spalte als Char-Array.
GetDataTypeName
Liefert den Datentyp der angegebenen Spalte.
GetDateTime
Liefert den Wert in der angegebenen Spalte als DateTime-Wert.
GetDecimal
Liefert den Wert in der angegebenen Spalte als Decimal-Wert.
GetDouble
Liefert den Wert in der angegebenen Spalte als Double-Wert.
GetFieldType
Holt den Type, der dem Datentyp des Objekts entspricht.
GetFloat
Liefert den Wert in der angegebenen Spalte als Float-Wert.
Tabelle 2.17: Eigenschaften und Methoden der OleDbDataReader-Klasse
92
Bonuskapitel 2
Methode
Beschreibung
GetGuid
Liefert den Wert in der angegebenen Spalte als GUID-Wert.
GetInt16
Liefert den Wert in der angegebenen Spalte als 16-Bit-Integer.
GetInt32
Liefert den Wert in der angegebenen Spalte als 32-Bit-Integer.
GetInt64
Liefert den Wert an der angegebenen Spalte als 64-Bit-Integer.
GetName
Liefert den Namen der angegebenen Spalte.
GetOrdinal
Holt den Index der Spalte, je nach dem Namen der Spalte.
GetSchemaTable
Liefert ein DataTable-Objekt, das die Spaltenmetadaten für dieses Objekt beschreibt.
GetString
Liefert den Wert in der angegebenen Spalte als String-Wert.
GetTimeSpan
Liefert den Wert in der angegebenen Spalte als TimeSpan-Wert.
GetValue
Liefert den Wert in der angegebenen Spalte in seinem nativen Format.
GetValues
Liefert alle Attribute des aktuellen Datensatzes und legt sie in einem angegebenen Array ab.
IsDBNull
Wird verwendet, um nicht vorhandene Werte anzuzeigen.
NextResult
Bewegt den Leser zum nächsten Ergebnis, wenn er die Resultate von StapelSQL-Statements liest.
Read
Bewegt den Leser zum nächsten Datensatz.
Tabelle 2.17: Eigenschaften und Methoden der OleDbDataReader-Klasse (Forts.)
OleDbError und OleDbErrorCollection Die OleDbError-Klasse sammelt Informationen über eine Warnung, die von der Datenquelle geschickt wurde. Tabelle C.18 listet die Eigenschaften dieser Klasse auf. Eigenschaft
Beschreibung
Message
Eine kurze Beschreibung des Fehlers.
NativeError
Die datenbankspezifische Fehlerinformation.
Tabelle 2.18: Eigenschaften der OleDbError-Klasse
93
Bonuskapitel 2
Eigenschaft
Beschreibung
Source
Holt das Objekt, das den Fehler verursachte.
SQLState
Holt den Standardfehlercode aus fünf Zeichen für die Datenbank.
Tabelle 2.18: Eigenschaften der OleDbError-Klasse (Forts.)
Tabelle C.19 listet Eigenschaften und Methode der OleDbErrorCollection-Klasse auf, die eine Auflistung von OleDbError-Objekten repräsentiert. Eigenschaft
Beschreibung
Count
Die Anzahl der Fehler in der Auflistung.
Item
Holt ein OleDbError-Objekt aus der Auflistung mit dem angegebenen Index.
Methode
Beschreibung
CopyTo
Kopiert die gesamte Auflistung in ein Array, beginnend am angegebenen Index.
Tabelle 2.19: Eigenschaften und Methode der OleDbErrorCollection-Klasse
OleDbParameter und OleDbParameterCollection Die OleDbParameter-Klasse repräsentiert einen Wert, der in Begleitung eines Datenbankbefehls übergeben wird, um zusätzliche Informationen oder Optionen bereitzustellen. Tabelle C.20 listet Eigenschaften und Methode dieser Klasse auf. Eigenschaft
Beschreibung
DbType
Der Datentyp der Datenquelle.
Direction
Gibt an, wie der Parameter benutzt wird. Kann Input, InputOutput, Output oder ReturnValue sein.
IsNullable
Gibt an, ob der Parameter einen NULL-Wert enthalten darf.
OleDbType
Legt den Type dieses Parameters fest. (Diese Eigenschaft existiert in der entsprechenden SqlParameter-Klasse nicht.)
Offset
Der Offset für die Value-Eigenschaft. (Diese Eigenschaft existiert nur in der SqlParameter-Klasse.)
Tabelle 2.20: Eigenschaften und Methode der OleDbParameter-Klasse
94
Bonuskapitel 2
Eigenschaft
Beschreibung
ParameterName
Der Name des Parameters.
Precision
Die maximale Anzahl von Ziffern, die zur Darstellung des Parameters benutzt wird.
Scale
Die Anzahl von Dezimalstellen, die zur Darstellung des Parameters benutzt wird.
Size
Die maximale Größe dieses Parameters.
SourceColumn
Der Name der Spalte in der Datenquelle, die auf das DataSet abgebildet wird, das für gelieferte Values verwendet wird.
SourceVersion
Legt die zu verwendende Zeilenversion fest, wenn Daten geladen werden.
Value
Der Wert des Parameters.
Methode
Beschreibung
ToString
Liefert den ParameterName.
Tabelle 2.20: Eigenschaften und Methode der OleDbParameter-Klasse(Forts.)
Tabelle C.21 listet Eigenschaften und Methoden der OleDbParameterCollection-Klasse auf, die eine Auflistung von OleDbParameter-Objekten repräsentiert. Eigenschaft
Beschreibung
Count
Die Anzahl der OleDbParameter-Objekte in der Auflistung.
Item
Holt einen OleDbParameter aus der Auflistung, entweder mit dem Namen des Parameters oder mit seinem Index in der Auflistung.
Methode
Beschreibung
Add
Fügt der Auflistung einen Parameter hinzu. Diese Methode ist überladen.
Clear
Löscht alle OleDbParameter-Objekte aus der Auflistung.
Contains
Gibt an, ob der OleDbParameter mit dem angegebenen Name in der Auflistung existiert.
CopyTo
Kopiert die gesamte Auflistung ab dem angegebenen Index in ein angegebenes Array.
IndexOf
Holt den Index des angegebenen Parameters. Diese Methode ist überladen.
Tabelle 2.21: Eigenschaften und Methoden der OleDbParameterCollection-Klasse
95
Bonuskapitel 2
Methode
Beschreibung
Insert
Fügt ein OleDbParameter-Objekt am angegebenen Index ein.
Remove
Entfernt den angegebenen Parameter aus der Auflistung. Diese Methode ist überladen.
RemoveAt
Entfernt den OleDbParameter am angegebenen Index.
Tabelle 2.21: Eigenschaften und Methoden der OleDbParameterCollection-Klasse(Forts.)
OleDbTransaction Die OleDbTransaction-Klasse repräsentiert eine Transaktion, die in der Datenquelle auftritt. Tabelle C.22 listet Eigenschaften und Methoden dieser Klasse auf. Eigenschaft
Beschreibung
Connection
Das OleDbConnection-Objekt, das mit dieser Transaktion verknüpft ist.
IsolationLevel
Die Ebene der Isolation für diese Transaktion. Kann Chaos, ReadCommitted (Standard), ReadUncommitted, RepeatableRead, Serializable oder Unspecified sein.
Methoden
Beschreibung
Begin
Startet die Transaktion. Nachfolgende Befehle und Änderungen werden alle in einem Transaktionsprotokoll mit aufgeschobener Bestätigung gespeichert und können jederzeit rückgängig gemacht werden.
Commit
Speichert alle Änderungen seit dem Aufruf von Begin.
RollBack
Macht alle Änderungen rückgängig, die an der Datenquelle seit dem Aufruf der Begin- oder Commit-Methode vorgenommen wurden.
Tabelle 2.22: Eigenschaften und Methoden der OleDbTransaction-Klasse
96