Programmieren in C#: Einführung


Kapitel 6: Ereignisse


Inhaltsverzeichnis

Dieses Buch ist unter einer Creative Commons-Lizenz lizensiert.


6.1 Allgemeines

Auf Benutzereingaben reagieren

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.


6.2 Delegates

Funktionszeiger

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 Ansicht und dann Ausgabe einblenden.


6.3 Ereignisse

Eingeschränkte Delegates

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.


6.4 Richtlinien

Empfehlungen zur Unterstützung von Ereignissen

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.


6.5 Benutzereingaben

Verarbeiten von Benutzereingaben in Windows Forms-Anwendungen

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.

Eigenschaftenfenster mit von der Liste unterstützten Ereignissen

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.

Windows Forms-Anwendung mit Ereignisunterstützung

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.


6.6 Aufgaben

Übung macht den Meister

Sie können die Lösungen zu allen Aufgaben in diesem Buch als ZIP-Datei erwerben.

  1. 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ü Beenden der Browser geschlossen, bei einem Klick auf Als Favorit speichern die aktuelle Webseite als Favorit gespeichert und bei einem Klick auf Info 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 Beenden ausgeführt wird, um das Programm zu beenden.

  2. 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.

  3. 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.

  4. 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ü Ansicht unter Weitere Fenster finden, kann dabei sehr hilfreich sein.