Programmieren in C#: Einführung
Dieses Buch ist unter einer Creative Commons-Lizenz lizensiert.
Die im vorherigen Kapitel 5, Komponenten entwickelte Windows-Anwendung besteht aus einem Fenster mit Menüleiste, Favoriten-Manager und einem Browser-Steuerelement. Während die Anwendung ausgeführt werden kann und ein modernes Fensters zu sehen ist, reagiert sie bisher auf keine Benutzereingaben. So können weder die Menüeinträge noch der Favoriten-Manager in irgendeiner Weise sinnvoll genutzt werden. In diesem Kapitel lernen Sie daher Ereignisse kennen, um auf Benutzereingaben wie Mausklicks zu reagieren.
Ereignisse basieren in der .NET-Welt auf sogenannten Delegates. Sie sind vergleichbar mit Funktionszeigern aus anderen Programmiersprachen. Während Referenzvariablen auf Objekte zeigen, zeigen Delegates auf Methoden. Und während über Referenzvariablen auf Eigenschaften und Methoden eines Objekts zugegriffen werden kann, können über Delegates Methoden aufgerufen werden, die an das Delegate gebunden sind.
Im Folgenden soll der aus den vorherigen Kapiteln bekannte Favoriten-Manager um einen Delegate erweitert werden. Methoden, die an diesen Delegate gebunden werden, sollen aufgerufen werden, wenn ein neuer Favorit dem Manager hinzugefügt wird. Auf diese Weise können andere Objekte informiert werden, wenn ein neuer Favorit gespeichert wird. Diese müssen lediglich eine Methode an den Delegate binden, die dann vom Favoriten-Manager automatisch aufgerufen wird.
using System; public class FavoriteManager { Favorite[] favs; public delegate void FavoriteEventHandler(Favorite fav); public FavoriteEventHandler FavoriteAdded; public void Add(string name, string url) { if (favs == null) { favs = new Favorite[] { new Favorite(name, new Uri(url)) }; } else { Favorite[] favs2 = new Favorite[favs.Length + 1]; Array.Copy(favs, favs2, favs.Length); favs2[favs.Length] = new Favorite(name, new Uri(url)); favs = favs2; } FavoriteAdded(favs[favs.Length - 1]); } }
In obiger Klasse FavoriteManager
wird ein Delegate namens FavoriteEventHandler
definiert. Dabei wird das Schlüsselwort delegate
verwendet und vor etwas gesetzt, das aussieht wie ein Methodenkopf. So ist hinter delegate
ein Rückgabewert, ein Name und eine Parameterliste angegeben.
Es ist wichtig zu verstehen, dass mit delegate
Datentypen definiert werden. FavoriteEventHandler
ist ein Datentyp, der verwendet werden kann, um eine Variable anzulegen. So wie Klassen Objekte beschreiben, beschreibt ein Delegate wie FavoriteEventHandler
Methoden. Er tut das über den Datentyp des Rückgabewerts und die Parameterliste. Der Delegate FavoriteEventHandler
beschreibt demnach Methoden, die einen Rückgabewert vom Typ void
besitzen und einen Parameter vom Typ Favorite
erwarten.
So wie Klassen instantiiert werden müssen, um Objekte zu erstellen, muss ein Delegate verwendet werden, um eine Variable anzulegen. Obige Klasse definiert eine derartige Variable: FavoriteAdded ist ein Delegate vom Typ FavoriteEventHandler
. Das bedeutet, dass Methoden an FavoriteAdded gebunden werden können, deren Signatur - also Rückgabewert und Parameterliste - mit der Definition von FavoriteEventHandler
übereinstimmt.
In der letzten Zeile der Methode Add()
wird auf FavoriteAdded so zugegriffen als handelt es sich um einen Methodennamen. So wird hinter FavoriteAdded in runden Klammern als Parameter der soeben in Add()
hinzugefügte Favorit angegeben. Dies bewirkt, dass alle Methoden, die an FavoriteAdded gebunden sind, nacheinander aufgerufen werden.
Wenn die Klasse FavoriteManager
so wie oben instantiiert wird, ist FavoriteAdded standardmäßig auf null
gesetzt. Würde über Add()
ein Favorit gespeichert werden, würde der Zugriff auf den Delegate zu einer Ausnahme führen. Ausnahmen werden Sie im Kapitel 9, Ausnahmen kennenlernen. Praktisch bedeutet dies jedoch, dass das Programm abstürzt. Deswegen ist es wichtig, vor einem Aufruf einer Delegate-Variable zu überprüfen, ob sie null
ist.
using System; using System.Diagnostics; public class FavoriteManager { Favorite[] favs; public delegate void FavoriteEventHandler(Favorite fav); public FavoriteEventHandler FavoriteAdded; public FavoriteManager() { FavoriteAdded = new FavoriteEventHandler(OnFavoriteAdded); FavoriteAdded += delegate(Favorite fav) { Trace.WriteLine("Favorite added: " + fav.Name); }; FavoriteAdded += (fav) => { Trace.WriteLine("Favorite added: " + fav.Name); }; } public void Add(string name, string url) { if (favs == null) { favs = new Favorite[] { new Favorite(name, new Uri(url)) }; } else { Favorite[] favs2 = new Favorite[favs.Length + 1]; Array.Copy(favs, favs2, favs.Length); favs2[favs.Length] = new Favorite(name, new Uri(url)); favs = favs2; } if (FavoriteAdded != null) { FavoriteAdded(favs[favs.Length - 1]); } } void OnFavoriteAdded(Favorite fav) { Trace.WriteLine("Favorite added: " + fav.Name); } }
C# bietet mehrere Möglichkeiten an, Methoden an Delegates zu binden. In C# 1.0 mussten Delegates mit new
instantiiert werden, um eine Methode als Parameter an den Konstruktor zu übergeben. Seit C# 2.0 ist es möglich, anonyme Funktionen zu erstellen, indem mit Hilfe des Schlüsselworts delegate
eine Funktion definiert wird, die keinen Namen hat. Seit C# 3.0 wiederum werden Lambda-Funktionen unterstützt. Es handelt sich hierbei ebenfalls um anonyme Funktionen, die jedoch nicht nur im Zusammenhang mit Delegates verwendet werden können, sondern auch in LINQ-Abfrage. Diese lernen Sie im Kapitel 8, LINQ kennen.
Der obige Favoriten-Manager bindet drei Funktionen an FavoriteAdded. Dies geschieht sowohl über eine explizite Instantiierung des Delegates als auch über eine anonyme Funktion und Lambda-Funktion. Während Sie so die verschiedenen Möglichkeiten kennenlernen, sollten Sie ab C# 3.0 Lambda-Funktionen vorziehen. Da Sie Lambda-Funktionen auch für LINQ-Abfragen einsetzen können, ist es von Vorteil, sich mit Lambda-Funktionen vertraut zu machen.
Bei einer expliziten Instantiierung mit new
muss dem Delegate als einziger Parameter der Name der Methode übergeben werden, die gebunden werden soll. Eine anonyme Funktion wiederum wird erstellt, indem Sie an Ort und Stelle eine Methode definieren, ohne ihr einen Rückgabewert oder Namen zu geben. Stattdessen muss das Schlüsselwort delegate
vor die Parameterliste gesetzt werden.
Lambda-Funktionen sehen am merkwürdigsten aus: Sie geben eine Parameterliste in runden Klammern an, der ein =>
folgt. Hinter diesem Operator folgt in geschweiften Klammern die Definition der Lambda-Funktion.
Beachten Sie, dass Sie bei Lambda-Funktionen keinen Datentypen für Parameter angeben müssen. Der Datentyp wird automatisch ermittelt. Bei anonymen Funktionen hingegen muss vor jedem Parameter der Datentyp angegeben sein.
Beachten Sie außerdem, dass die drei Funktionen auf unterschiedliche Weise an den Delegate FavoriteAdded gebunden werden. Während die erste Funktion mit =
an FavoriteAdded gebunden wird, wird in den darauffolgenden Zeilen der Operator +=
verwendet. Dieser Operator bedeutet, dass die anonyme Funktion und die Lambda-Funktion zusätzlich an den Delegate FavoriteAdded gebunden werden, ohne die Bindung an andere Funktionen aufzuheben, wie es bei einer Zuweisung mit =
geschieht. Wird für obigen Favoriten-Manager die Methode Add()
aufgerufen, würden alle drei gebundenen Funktionen nacheinander ausgeführt werden - und zwar in der Reihenfolge, in der sie gebunden wurden.
Im obigen Beispielcode greifen die drei Funktionen lediglich auf die Klasse Trace
zu. Diese Klasse stammt aus dem Namensraum System.Diagnostics und kann verwendet werden, um Meldungen auszugeben, die es ermöglichen, die Funktionsweise einer Anwendung zu überwachen. So werden zum Beispiel die Meldungen in das Ausgabefenster von Visual C# geschrieben, wenn die Anwendung ausgeführt wird. Es handelt sich also bei Trace
um eine Art Konsole für Entwickler, die einen Blick in das Innere einer Anwendung zur Laufzeit erlaubt. Sie können das Ausgabefenster über und dann einblenden.
Die Ereignisverarbeitung in C# basiert auf Delegates, wie Sie sie im vorherigen Abschnitt kennengelernt haben. C# stellt jedoch speziell für Ereignisse ein Schlüsselwort namens event
zur Verfügung, das Delegates in gewisser Weise einschränkt. Es wird einer Delegate-Variablen wie FavoriteAdded einfach vorangestellt.
Delegates wie FavoriteAdded sind vergleichbar mit Feldern. Es handelt sich um Variablen, die nicht wie Referenz- oder Wertvariablen Zugriff auf Objekte bieten, sondern auf Methoden. Während Felder gemäß dem Prinzip der Datenkapselung privat sein sollten, kann dieser Empfehlung bei Delegates nur schwer gefolgt werden. Denn wäre eine Delegate-Variable wie FavoriteAdded privat, könnten keine Methoden von außerhalb der Klasse FavoriteManager
an die Variable gebunden werden. Das widerspricht aber genau dem Sinn und Zweck von Delegate-Variablen: Es sollen schließlich beliebige Methoden gebunden werden dürfen, damit alle Objekte, die informiert werden möchten, wenn ein neuer Favorit gespeichert wird, auch informiert werden können.
Indem das Schlüsselwort event
vor einer Delegate-Variablen angegeben wird, entspricht die Variable nicht mehr einem Feld, sondern einer Eigenschaft:
So wie Eigenschaften get
- und set
-Accessoren besitzen, besitzen Delegate-Variablen, die mit event
definiert sind, add
- und remove
-Accessoren. Diese können überschrieben werden, um zum Beispiel eine Meldung auszugeben, wenn eine Methode gebunden wird oder die Bindung einer Methode aufgehoben wird.
So wie Interfaces keine Felder, aber Eigenschaften definieren dürfen, können in ihnen keine Delegate-Variablen, aber Event-Variablen verwendet werden.
Der Aufruf von Methoden, die an Event-Variablen gebunden sind, ist ausschließlich in der Klasse möglich, in der die Event-Variable definiert ist. Andere Klassen können Methoden an eine Event-Variable binden, aber kein Ereignis auslösen.
Methoden können ausschließlich über +=
gebunden und über -=
entfernt werden. Es ist nicht mehr möglich, mit =
eine Methode zu binden.
Diese Einschränkungen machen Sinn, wenn Sie eine Variable nicht nur als Delegate-Variable verwenden möchten, sondern im Sinne der Ereignisverarbeitung als Event-Variable. Da Delegates in der Praxis fast immer zur Definition von Event-Variablen verwendet werden, sollten Sie entsprechend häufig auf das Schlüsselwort event
zugreifen und es einsetzen.
using System; using System.Diagnostics; public class FavoriteManager { Favorite[] favs; public delegate void FavoriteEventHandler(Favorite fav); private event FavoriteEventHandler FavoriteAddedInternal; public event FavoriteEventHandler FavoriteAdded { add { Trace.WriteLine("Eventhandler added"); FavoriteAddedInternal += value; } remove { Trace.WriteLine("Eventhandler removed"); FavoriteAddedInternal -= value; } } public FavoriteManager() { FavoriteAdded += new FavoriteEventHandler(OnFavoriteAdded); FavoriteAdded += delegate(Favorite fav) { Trace.WriteLine("Favorite added: " + fav.Name); }; FavoriteAdded += (fav) => { Trace.WriteLine("Favorite added: " + fav.Name); }; } public void Add(string name, string url) { if (favs == null) { favs = new Favorite[] { new Favorite(name, new Uri(url)) }; } else { Favorite[] favs2 = new Favorite[favs.Length + 1]; Array.Copy(favs, favs2, favs.Length); favs2[favs.Length] = new Favorite(name, new Uri(url)); favs = favs2; } if (FavoriteAddedInternal != null) { FavoriteAddedInternal(favs[favs.Length - 1]); } } void OnFavoriteAdded(Favorite fav) { Trace.WriteLine("Favorite added: " + fav.Name); } }
Im Favoriten-Manager wird nun das Schlüsselwort event
verwendet. Für die Event-Eigenschaft sind außerdem die Accessoren add
und remove
definiert worden, um über die Klasse Trace
eine Meldung auszugeben, wenn eine Methode gebunden oder entfernt wird. Sie müssen in diesem Fall selbst dafür sorgen, dass die entsprechende Methode tatsächlich gebunden oder entfernt wird. Im obigen Beispielcode geschieht dies mit Hilfe der Event-Variablen FavoriteAddedInternal, an die die Methoden weitergeleitet werden.
Beachten Sie, dass alle Methoden nun mehr über +=
an die Event-Eigenschaft gebunden werden. Der Zuweisungsoperator kann nicht mehr verwendet werden.
Beachten Sie außerdem, dass in der letzten Zeile von Add()
das Ereignis über das Event-Feld FavoriteAddedInternal ausgelöst wird. Das ist zwingend notwendig: Wenn Sie add
und remove
überschreiben, können Sie die Event-Eigenschaft nicht mehr verwenden, um ein Ereignis auszulösen. Das ist insofern nicht tragisch als dass Sie die Methoden, die in add
und remove
gebunden oder entfernt werden sollen, sowieso anderweitig speichern müssen. Im obigen Favoriten-Manager geschieht dies über das Event-Feld FavoriteAddedInternal, über das dann auch das Ereignis ausgelöst werden kann.
Während Sie nun wissen, wie Sie delegate
und event
verwenden, um Ereignisse zu unterstützen, gibt es Richtlinien im .NET-Framework, wie die Unterstützung idealerweise aussehen soll. Wenn Sie diese Richtlinien kennen und beherzigen, können Sie die Ereignisse, die Ihre Klasse unterstützen, an die Ereignisse angleichen, wie sie vom .NET-Framework unterstützt werden.
Das .NET-Framework sieht vor, dass Delegates zur Ereignisverarbeitung zwei Parameter besitzen: Der erste Parameter soll eine Referenzvariable auf das Objekt sein, das das Ereignis auslöst. Der zweite Parameter soll beliebige Daten zur Verfügung stellen, die das Ereignis kennzeichnen und für die Ereignisverarbeitung wichtig sein können.
Im Namensraum System ist ein derartiger Delegate definiert, der den Idealzustand repräsentiert. Dieser Delegate heißt EventHandler
und ist wie folgt definiert:
delegate void EventHandler(Object sender, EventArgs e);
Wenn Sie eine Event-Variable vom Typ EventHandler
erstellen, müssen demnach Methoden, die an diese Variable gebunden werden, zwei Parameter vom Typ Object
und EventArgs
erwarten. Wird das entsprechende Ereignis ausgelöst, kann über den ersten Parameter herausgefunden werden, wer das Ereignis ausgelöst hat. Über den zweiten Parameter werden zusätzliche Informationen zum Ereignis bereitgestellt.
Wenn Sie sich die Klasse EventArgs
, die ebenfalls im Namensraum System definiert ist, in der Dokumentation anschauen, stellen Sie fest, dass diese Klasse keine Eigenschaften oder Methoden anbietet - abgesehen von einer statischen Eigenschaft Empty, die ein Objekt vom Typ EventArgs
zurückgibt. Im Folgenden soll nun der Favoriten-Manager das Delegate EventHandler
verwenden.
using System; using System.Diagnostics; public class FavoriteManager { Favorite[] favs; public event EventHandler FavoriteAdded; public FavoriteManager() { FavoriteAdded += new EventHandler(OnFavoriteAdded); FavoriteAdded += delegate(Object sender, EventArgs e) { Trace.WriteLine("Favorite added"; }; FavoriteAdded += (sender, e) => { Trace.WriteLine("Favorite added"); }; } public void Add(string name, string url) { if (favs == null) { favs = new Favorite[] { new Favorite(name, new Uri(url)) }; } else { Favorite[] favs2 = new Favorite[favs.Length + 1]; Array.Copy(favs, favs2, favs.Length); favs2[favs.Length] = new Favorite(name, new Uri(url)); favs = favs2; } if (FavoriteAdded != null) { FavoriteAdded(this, EventArgs.Empty); } } void OnFavoriteAdded(Object sender, EventArgs e) { Trace.WriteLine("Favorite added"); } }
Da der Delegate EventHandler
vom .NET-Framework zur Verfügung gestellt wird, wird kein eigener Delegate wie FavoriteEventHandler
mehr benötigt. Da EventHandler
jedoch anders definiert ist, müssen die Signaturen der Event-Handler angepasst werden. Das ist insofern ein Problem als dass es keine Möglichkeit mehr gibt, den Favoriten zu übergeben, der soeben dem Favoriten-Manager hinzufügt wurde. Wenn die Event-Handler aufgerufen werden, wissen sie, dass ein Favorit hinzugefügt wurde - aber sie wissen nicht, welcher.
Da die Klasse EventArgs
keine Möglichkeit vorsieht, einen Favoriten zu speichern, muss eine neue Klasse erstellt und von ihr abgeleitet werden.
using System; public class FavoriteEventArgs : EventArgs { Favorite fav; public FavoriteEventArgs(Favorite fav) { this.fav = fav; } public Favorite RelatedFavorite { get { return fav; } } }
Die Klasse FavoriteEventArgs
stellt eine Eigenschaft RelatedFavorite zur Verfügung, damit Event-Handler herausfinden können, welcher Favorit zum Favoriten-Manager hinzugefügt wurde. Um diese neue Klasse zur Ereignisverarbeitung einzusetzen, kann jedoch nicht mehr auf den Delegate EventHandler
im Favoriten-Manager zugegriffen werden. Es wird daher ein neuer Delegate FavoriteEventHandler
definiert.
using System; using System.Diagnostics; public class FavoriteManager { Favorite[] favs; public delegate void FavoriteEventHandler(Object sender, FavoriteEventArgs e); public event FavoriteEventHandler FavoriteAdded; public FavoriteManager() { FavoriteAdded += new FavoriteEventHandler(OnFavoriteAdded); FavoriteAdded += delegate(Object sender, FavoriteEventArgs e) { Trace.WriteLine("Favorite added: " + e.RelatedFavorite.Name); }; FavoriteAdded += (sender, e) => { Trace.WriteLine("Favorite added: " + e.RelatedFavorite.Name); }; } public void Add(string name, string url) { if (favs == null) { favs = new Favorite[] { new Favorite(name, new Uri(url)) }; } else { Favorite[] favs2 = new Favorite[favs.Length + 1]; Array.Copy(favs, favs2, favs.Length); favs2[favs.Length] = new Favorite(name, new Uri(url)); favs = favs2; } if (FavoriteAdded != null) { FavoriteAdded(this, new FavoriteEventArgs(favs[favs.Length - 1])); } } void OnFavoriteAdded(Object sender, FavoriteEventArgs e) { Trace.WriteLine("Favorite added: " + e.RelatedFavorite.Name); } }
Ab sofort können die Event-Handler wieder den Namen des Favoriten ausgeben, der dem Favoriten-Manager hinzugefügt wurde. Weil der zweite Parameter nun den Datentyp FavoriteEventArgs
hat, steht über die Eigenschaft RelatedFavorite der entsprechende Favorit in den Event-Handlern zur Verfügung.
Wenn Ihre Klassen Ereignisse unterstützen sollen, sollten Sie so vorgehen, wie Sie es in diesem Abschnitt gelernt haben. Dies ist die empfohlene Vorgehensweise im .NET-Framework. Außerdem sollten Sie folgende Regeln einhalten:
Beim Auslösen eines Ereignisses sollte der erste Parameter vom Typ Object
niemals null
sein, sondern immer auf das Objekt zeigen, dass das Ereignis ausgelöst hat. Einzige Ausnahme sind statische Ereignisse - also Event-Eigenschaften, die mit static
definiert sind. Weil es in diesem Fall unter Umständen kein Objekt gibt, das das Ereignis auslöst, darf der erste Parameter null
sein.
Wenn Sie die generische Programmierung kennen, erstellen Sie keine neue Klasse, die Sie von EventArgs
ableiten. Verwenden Sie stattdessen die generische Klasse EventHandler
. Die generische Programmierung lernen Sie im Kapitel 7, Generika kennen.
In der Dokumentation zum Ereignisentwurf finden Sie eine vollständige Übersicht zu den Entwurfsrichtlinien.
Nachdem Sie nun die Ereignisverarbeitung kennengelernt haben, soll der im vorherigen Kapitel entwickelte Browser so erweitert werden, dass er auf Benutzereingaben reagiert. So soll zum Beispiel bei einem Klick auf einen Favoriten im Favoriten-Manager die gewünschte Webseite angezeigt werden.
Der Favoriten-Manager wurde im vorherigen Kapitel zu einem Steuerelement umgebaut. Dieses Steuerelement verwendete ein Listen-Steuerelement vom Typ ListBox
zur Anzeige der Favoriten. Wenn Sie sich die Dokumentation der ListBox
ansehen, sehen Sie, dass das Steuerelement ein Ereignis namens SelectedIndexChanged unterstützt. Das Steuerelement des Favoriten-Managers wird nun in einem ersten Schritt derart erweitert, dass eine Methode an die Event-Eigenschaft SelectedIndexChanged gebunden wird.
Sie können die entsprechende Änderung des Favoriten-Managers über das Eigenschaftenfenster der Liste vornehmen. Öffnen Sie dazu das Steuerelement des Favoriten-Managers im Ansicht-Designer und wählen Sie die Liste aus. Klicken Sie dann im Eigenschaftenfenster auf das Blitz-Symbol, um die Ereignisse zu sehen, die von der Liste unterstützt werden. Wenn Sie dann einen Doppeklick in das Feld von SelectedIndexChanged machen, wird der entsprechende Event-Handler automatisch zur Klasse FavoriteManager
hinzugefügt. Die Methode wird auch in InitializeComponent()
automatisch an die Event-Eigenschaft SelectedIndexChanged der Liste gebunden.
using System; using System.Windows.Forms; public class FavoriteManager : UserControl { private ListBox listBox1; Favorite[] favs; public delegate void FavoriteEventHandler(Object sender, FavoriteEventArgs e); public event FavoriteEventHandler FavoriteChanged; public FavoriteManager() { InitializeComponent(); listBox1.DisplayMember = "Name"; } public void Add(string name, string url) { if (favs == null) { favs = new Favorite[] { new Favorite(name, new Uri(url)) }; listBox1.DataSource = favs; } else { Favorite[] favs2 = new Favorite[favs.Length + 1]; Array.Copy(favs, favs2, favs.Length); favs2[favs.Length] = new Favorite(name, new Uri(url)); favs = favs2; listBox1.DataSource = favs; } } public Favorite[] Favorites { set { favs = value; listBox1.DataSource = favs; } get { return favs; } } private void InitializeComponent() { this.listBox1 = new System.Windows.Forms.ListBox(); this.SuspendLayout(); // // listBox1 // this.listBox1.Dock = System.Windows.Forms.DockStyle.Fill; this.listBox1.FormattingEnabled = true; this.listBox1.Location = new System.Drawing.Point(0, 0); this.listBox1.Name = "listBox"; this.listBox1.Size = new System.Drawing.Size(150, 147); this.listBox1.TabIndex = 0; this.listBox1.SelectedIndexChanged += new System.EventHandler(this.listBox1_SelectedIndexChanged); // // FavoriteManager // this.Controls.Add(this.listBox1); this.Name = "FavoriteManager"; this.ResumeLayout(false); } private void listBox1_SelectedIndexChanged(object sender, EventArgs e) { if (FavoriteChanged != null) { FavoriteChanged(this, new FavoriteEventArgs((Favorite)listBox1.SelectedItem)); } } }
Wenn ein Anwender auf die Liste klickt und einen Eintrag auswählt, wird nun die Methode listBox1_SelectedIndexChanged()
im Favoriten-Manager aufgerufen. Was soll nun in dieser Methode passieren?
Der Favoriten-Manager ist ein Steuerelement, das wiederverwendet werden kann. Wenn das Steuerelement in einer Windows Forms-Anwendung wie einem Browser eingesetzt wird, hängt es demnach von dieser Anwendung ab, was bei der Auswahl eines Favoriten passieren soll. Das bedeutet genaugenommen, dass nicht der Favoriten-Manager selbst informiert werden muss, wenn ein Favorit ausgewählt wurde, sondern die entsprechende Anwendung, die den Favoriten-Manager einsetzt.
Der Trick ist, das Ereignis, das von der Liste ausgelöst wird, weiterzuleiten. Weil die Liste nur in der Klasse FavoriteManager
verwendet werden kann - so ist das entsprechende Objekt in einem privaten Feld namens listBox1 verankert - kann keine andere Klasse eine Methode an die Event-Eigenschaft SelectedIndexChanged der Liste binden. Der Favoriten-Manager kann jedoch ein entsprechendes Ereignis definieren, das anderen Klassen zugänglich gemacht werden kann.
Die Klasse FavoriteManager
greift daher auf die in diesem Kapitel entwickelte Klasse FavoriteEventArgs
zurück und definiert einen Delegate FavoriteEventHandler
. Es wird außerdem eine Event-Eigenschaft FavoriteChanged vom Typ FavoriteEventHandler
angelegt. Diese Event-Eigenschaft ist öffentlich, damit Programme, in denen der Favoriten-Manager eingesetzt wird, Event-Handler binden können.
Innerhalb der Methode listBox1_SelectedIndexChanged()
muss nun lediglich der aktuell ausgewählte Eintrag der Liste ermittelt werden, um dann das Ereignis mit dem entsprechenden Favoriten über FavoriteChanged weiterzuleiten. Dazu kann auf die Eigenschaft SelectedValue der Liste zugegriffen werden. Da diese Eigenschaft jedoch den Datentyp Object
hat, an den Konstruktor von FavoriteEventArgs
jedoch ein Objekt vom Typ Favorite
übergeben werden muss, muss der Datentyp gecastet werden.
Beachten Sie, dass Sie für ein erfolgreiches Casting unbedingt sichergehen müssen, dass das entsprechende Objekt tatsächlich in den Datentyp umgewandelt werden kann, der in Klammern angegeben wird. Da wir wissen, dass die Liste über die Eigenschaft DataSource an ein Array gebunden ist, das Objekte vom Typ Favorite
speichert, ist sichergestellt, dass das Objekt, das von SelectedValue zurückgegeben wird, zu Favorite
gecastet werden kann.
Nachdem das Steuerelement des Favoriten-Managers nun über FavoriteChanged ein Ereignis unterstützt, soll dieses im Browser verwendet werden. Öffnen Sie dazu das Projekt, in dem Sie die Windows Forms-Anwendung entwickelt haben. Öffnen Sie dann den Ansicht-Designer des Formulas. Wenn Sie das Steuerelement des Favoriten-Managers auswählen und einen Blick in das Eigenschaftenfenster werfen, sehen Sie dort das neue Ereignis FavoriteChanged. Mit einem Doppelklick ins leere Feld wird von Visual C# automatisch ein entsprechender Event-Handler in der Klasse Form1
definiert, der außerdem ebenfalls automatisch in InitializeComponent()
an das Ereignis gebunden wird.
using System; using System.Windows.Forms; namespace Browser { public partial class Form1 : Form { public Form1() { InitializeComponent(); } private void favoriteManager1_FavoriteChanged(object sender, FavoriteEventArgs e) { webBrowser1.Navigate(e.RelatedFavorite.URL); } } }
Die Methode favoriteManager1_FavoriteChanged()
soll nun derart implementiert werden, dass bei einem Mausklick auf einen Favoriten die entsprechende Webseite im WebBrowser-Steuerelement angezeigt wird. Dazu muss lediglich über die entsprechende Referenzvariable auf das Steuerelement zugegriffen werden und die Methode Navigate()
aufgerufen werden. Da dieser Methode als Parameter ein Objekt vom Typ Uri
übergeben werden kann, kann über den Parameter e auf RelatedFavorite und dann auf URL zugegriffen werden, um die Adresse weiterzureichen.
Fügen Sie dem Favoriten-Manager über den Ansicht-Designer ein paar Favoriten hinzu. Führen Sie dann die Windows Forms-Anwendung zum Test aus. Sie sollten nicht nur alle Ihre voreingestellten Favoriten in der Liste sehen. Bei einem Mausklick auf einen Eintrag sollte außerdem die entsprechende Webseite im WebBrowser-Steuerelement geöffnet werden.
Sie können die Lösungen zu allen Aufgaben in diesem Buch als ZIP-Datei erwerben.
Ihre Lösung zur Aufgabe 2 aus Abschnitt 5.8, „Aufgaben“ soll auf Benutzereingaben reagieren. So soll nicht nur wie in diesem Kapitel gezeigt bei einem Klick auf einen Favoriten im Favoriten-Manager die entsprechende Webseite angezeigt werden. Es soll außerdem bei einem Klick auf das Menü der Browser geschlossen, bei einem Klick auf die aktuelle Webseite als Favorit gespeichert und bei einem Klick auf ein Dialogfenster mit einer kurzen Info zum Programm angezeigt werden. Diese Info kann zum Beispiel ein Copyright-Hinweis sein.
Sie können alle Event-Handler über den Ansicht-Designer hinzufügen, indem Sie die entsprechenden Komponenten auswählen und im Eigenschaftenfenster auf die leeren Felder neben den Ereignissen doppelklicken. Visual C# erstellt daraufhin automatisch die notwendigen Methoden. Um diese Methoden zu definieren, schlagen Sie in der Dokumentation der jeweiligen Klassen nach, um herauszufinden, welche Eigenschaften und Methoden angeboten werden, auf die Sie zugreifen können. Zum Beispiel bietet die Klasse Form
eine Methode an, um ein Fenster zu schließen. Sie können diese Methode im Event-Handler aufrufen, der bei einem Mausklick auf ausgeführt wird, um das Programm zu beenden.
Erweitern Sie Ihre Lösung zur Aufgabe 1, damit bei einem Wechsel der Webseite im WebBrowser-Steuerelement die neue Adresse in der Statusleiste angezeigt wird. Dabei soll die Anzeige in der Statusleiste nicht nur dann aktualisiert werden, wenn der Anwender auf einen Favoriten klickt, sondern auch bei einem Klick auf einen Link in einer Webseite.
Damit Sie wissen, wann die Webseite im WebBrowser-Steuerelement wechselt und Sie die Statusleiste aktualisieren müssen, müssen Sie nach einem entsprechenden Ereignis suchen, das vom WebBrowser-Steuerelement angeboten wird.
Erweitern Sie Ihre Lösung zur Aufgabe 2 insofern, dass bei einem Druck auf die ENTF-Taste der augenblicklich markierte Favorit aus dem Favoriten-Manager entfernt wird.
Sie müssen nun lediglich den Favoriten-Manager ändern, denn dieser muss informiert werden, wenn die ENTF-Taste in der Liste gedrückt wird. Suchen Sie nach einem passenden Ereignis in der Klasse ListBox
, um im entsprechenden Event-Handler den augenblicklich markierten Favoriten zu löschen. Testen Sie Ihre Anwendung, indem Sie alle Favoriten mit der ENTF-Taste löschen.
Erweitern Sie Ihre Lösung zur Aufgabe 3, so dass sich der Favoriten-Manager in seiner Breite flexibel ändern lässt. Anwender sollen mit der Maus den Rand des Favoriten-Managers anklicken können, um die Breite zu ändern.
Lassen Sie sich nicht täuschen und suchen Sie nicht nach einem passenden Ereignis. Das .NET-Framework stellt ein SplitContainer-Steuerelement bereit, mit dem ein Container in zwei Bereiche aufgeteilt werden kann. Diese Bereiche können in ihrer Größe geändert werden, ohne dass Sie eine einzige Zeile programmieren müssen. Sie müssen lediglich den SplitContainer von der Toolbox ins Fenster ziehen und die Steuerelemente neu anordnen. Die Dokumentgliederung, die Sie im Menü
unter finden, kann dabei sehr hilfreich sein.Copyright © 2009, 2010 Boris Schäling