Startseite  Inhaltsverzeichnis  <<  >>  

Kapitel 4 - Ereignisbehandlung

4.1 Grundlagen

Ein wesentliches Merkmal von XAML ist die Möglichkeit, Oberflächen getrennt von der Anwendungslogik zu erstellen. Man kann sich eine XAML-Datei wie eine Ressourcendatei für die Benutzeroberfläche vorstellen, die mit speziell dafür vorgesehenen Tools bearbeitet werden kann, z.B. mit dem WPF Designer oder Expression Blend. Da die Programmlogik normalerweise nicht in XAML verankert ist, muss es möglich sein, mit einer Programmiersprache wie C# oder Visual Basic diese zu formulieren und mit einer XAML-Datei zu verknüpfen. Da mit XAML eine weitere Komponente bei der Ereignisbehandlung hinzugekommen ist, existieren gleich mehrere Varianten, um auf Ereignisse zu reagieren:

nach oben

4.2 Code in der XAML-Datei

Eine Lösung XAML- und C#-Code miteinander zu verbinden besteht darin, beides direkt in der XAML-Datei einzusetzen. Dadurch ist keine Code-Behind-Datei mehr notwendig. Ein Tool, welches XAML-Dateien direkt verarbeitet muss nun in der Lage sein, den Programmcode zu übersetzen und auszuführen. Der Internet Explorer, der ja XAML-Dateien laden und anzeigen kann, ist z.B. nicht in der Lage, den Code zu übersetzen. Folglich wird die XAML-Datei von ihm nicht interpretiert. Das Visual Studio hingegen übersetzt den Code in der XAML-Datei mitsamt dem Code einer möglichen Code-Behind-Datei und bindet ihn als binäre Ressource in die Anwendung.

Ein Code-Abschnitt innerhalb von XAML muss in ein x:Code-Element eingebettet werden. Da der Code beliebig sein kann, also auch Operatoren wie < und > enthalten kann und er sich in einer XML-Datei befindet (XAML ist ja letzten Endes XML) sollte dieser Code-Abschnitt in einen CDATA-Abschnitt eingeschlossen werden. Dadurch wird der Inhalt durch einen XML-Parser (also auch bei der Interpretation der XAML-Datei) nicht als XML-Daten ausgewertet. Ansonsten ist die Verwendung des CDATA-Abschnitts optional.

Eine Beschränkung dieser Lösung ist beispielsweise, dass nur Code für die partielle Klasse eingefügt werden kann, welche zur XAML-Datei gehört. Separate Klassen können z.B. nicht in einem solchen Abschnitt deklariert werden (ausgenommen innere Klassen).

BEISPIEL: EreignisCodeInXamlProj.zip - Download

Innerhalb einer XAML-Datei werden ein Button und eine TextBox innerhalb eines StackPanels eingefügt. Der Button ist mit dem Text Klick mich beschriftet, die TextBox enthält den Text Hallo. Um auf das Klicken des Buttons zu reagieren, muss das Attribut Click, welches hier dem Click-Ereignis entspricht, mit dem Namen einer Methode verknüpft werden, die auf dieses Ereignis reagiert. In diesem Fall heißt die Methode ClickMich(). Durch den Klick auf den Button soll der Inhalt der TextBox geändert werden. Damit Sie das TextBox-Element ansprechen können, müssen Sie einen Namen für die TextBox vergeben. Es wird der Name TbInfo über das Attribut Name zugewiesen.

Um Code in der XAML-Datei unterzubringen, wird das Element x:Code verwendet. In einem CDATA-Abschnitt wird dann der Code für die Ereignisbehandlung untergebracht, der selbsterklärend sein sollte.

Ereignisse in XAML verpackt

Abbildung 4.1: Ereignisse in XAML verpackt

<StackPanel Margin="10" HorizontalAlignment="Left">
  <Button Click="ClickMich" Width="100">Klick mich</Button>
  <x:Code>
    <![CDATA[
    void ClickMich(object sender, RoutedEventArgs args)
    {
      TbInfo.Text = "Der Button wurde geklickt";
    }
    ]]>
  </x:Code>
  <TextBox Name="TbInfo" Width="140">Hallo</TextBox>
</StackPanel>
Listing 4.1: Beispiele\Kap04\EreignisCodeInXamlProj\Window1.xaml

HINWEIS: Die Verwendung von Code innerhalb der XAML-Datei ist zwar eine mögliche Lösung für eine Ereignisbehandlung, allerdings sollte davon eher weniger Gebrauch gemacht werden. Ziel sollte es stattdessen sein, eine saubere Trennung zwischen dem Design der Oberfläche und der dahinter liegenden Programmlogik zu schaffen.

nach oben

4.3 Getrennte Ereignisbehandlung

Der übliche Weg der Ereignisbehandlung ist die Verknüpfung eines Ereignisses mit einer Komponente in XAML und die Kodierung der Methode in der Code-Behind-Datei. In der XAML-Datei muss dazu in der Komponente, im Attribut das den Namen des Ereignisses trägt, der Name der Ereignisroutine angegeben werden - wie auch schon bei der Verarbeitung innerhalb von XAML.

Der Code wird jetzt aber in der Code-Behind-Datei hinterlegt. Dazu muss die Methode entsprechend der Signatur des Ereignisses erstellt und in der partiellen Klasse der Code-Behind-Datei, welche der XAML-Datei zugeordnet ist, implementiert werden.

BEISPIEL: EreignisCodeInCodeBehindProj.zip - Download

Das folgende Beispiel verknüpft wiederum das Click-Ereignis eines Buttons mit einer Methode namens ClickMich(). Diesmal wird der Code allerdings in die Code-Behind-Datei des Fensters ausgelagert.

<StackPanel Margin="10" HorizontalAlignment="Left">
  <Button Click="ClickMich" Width="100">Klick mich</Button>
  <TextBox Name="TbInfo" Width="140">Hallo</TextBox>
</StackPanel>
Listing 4.2: Beispiele\Kap04\EreignisCodeInCodeBehindProj\Window1.xaml

Die Methode ClickMich() wird in der Fensterklasse Window1 deklariert. Der Name der TextBox steht ebenfalls im Code zur Verfügung, auch wenn er in der XAML-Datei über das Attribut Name vergeben wurde.

namespace EreignisCodeInCodeBehindProj
{
  public partial class Window1: System.Windows.Window
  {
    public Window1()
    {
      InitializeComponent();
    }
    private void ClickMich(object sender, RoutedEventArgs e)
    {
      TbInfo.Text = "Der Button wurde geklickt";
    }
  }
}
Listing 4.3: Beispiele\Kap04\EreignisCodeInCodeBehindProj\Window1.xaml.cs

4.3.1 Anwendungsereignisse
Der Rahmen einer .NET 3.0-Windows-Anwendung besteht aus einem Element bzw. einem Objekt vom Typ Application aus dem Namespace System.Windows. In der XAML-Datei mit dem Standardnamen App.xaml befindet sich ein wesentlicher Eintrag. Gemeint ist das Attribut StartupUri, welches den Namen der Fensterklasse enthält, von der eine Instanz nach dem Start angezeigt werden soll, das Hauptfenster sozusagen. Man kann hier aber auch auf andere Anwendungsereignisse reagieren, z.B. wenn die Anwendung geladen oder beendet wird. Die Klasse Application enthält dazu verschiedene Ereignisse, von denen einige in der folgenden Tabelle gezeigt werden.

EreignisBeschreibung
ActivatedDie Anwendung gelangt in den Vordergrund.
DeactivatedDie Anwendung ist keine Vordergrundanwendung mehr.
DispatcherUnhandledExceptionWird ausgelöst, wenn eine nicht behandelte Exception aufgetreten ist.
ExitDie Anwendung wird beendet.
SessionEndingSoll die aktuelle Windowssitzung beendet werden, wird dieses Ereignis ausgelöst. Die Anwendung hat noch die Möglichkeit, das Beenden zu unterbinden. Beachten Sie allerdings dass es Verfahren gibt, auch ohne Rücksicht auf andere Anwendungen Windows zu beenden (z.B. durch einen Blue Screen).
StartupDie Run()-Methode der Anwendung wurde aufgerufen, d.h. die Anwendung wurde gestartet.

Tabelle 4.1: Einige Ereignisse der Klasse Application

BEISPIEL: AnwendungsEreignisseProj.zip - Download

Wenn Sie eine Windows-Anwendung mit der WPF erzeugen, wird das Hauptfenster über den XAML-Code StartupUri="Window1.xaml" in der Datei App.xaml festgelegt. Sie können das Hauptfenster aber auch anders erzeugen, z.B. manuell im Ereignis Startup. Dazu wird die Angabe der StartupUri entfernt und stattdessen eine Verknüpfung zum Ereignis Startup hinzugefügt. Alternativ lassen Sie auch diese Ereignisverknüpfung weg und überschreiben stattdessen die Methode OnStartup() der Klasse Application, welche automatisch beim Ereignis Startup aufgerufen wird. In diesem Fall gibt es keinen Parameter vom Typ object und Sie müssen zum Überschreiben protected override angeben. Kurz gesagt gibt es zahlreiche Varianten, Initialisierungscode unterzubringen.

Um nicht behandelte Exceptions abzufangen, kann auf das Ereignis DispatcherUnhandledException reagiert werden. Beim Auftreten einer solchen unbehandelten Exception wird jetzt die Methode MainExHandler() aufgerufen. Darin kann die Exception ausgewertet und durch das Setzen der Eigenschaft Handled des Parameters vom Typ DispatcherUnhandledExceptionEventArgs auf true als behandelt markiert werden. Die Exception selbst wird im Hauptfenster der Anwendung durch einen Klick auf einen Button ausgelöst.

<Application...
<!-- In XAML sind dieser und der folg. Kommentar ungültig! -->
<!-- StartupUri="Window1.xaml" -->
     Startup="OnStartup"
     DispatcherUnhandledException="MainExHandler">
  <Application.Resources>
  </Application.Resources>
</Application>
Listing 4.4: Beispiele\Kap04\AnwendungsEreignisseProj\MyApp.xaml

Entweder Sie implementieren eine Ereignisbehandlung wie sie im folgenden Listing gezeigt wird oder Sie entfernen die Verknüpfung zwischen dem Startereignis Startup und der Methode OnStartup(), kommentieren die Methode OnStartup() im folgenden Code-Teil aus und verwenden die überschriebene Methode OnStartup(), die hier denselben Namen besitzt.

public partial class App: System.Windows.Application
{
  void OnStartup(object sender, StartupEventArgs e)
  {
    Window1 mainWindow = new Window1();
    mainWindow.Show();
  }
  // protected override void OnStartup(StartupEventArgs e)
  // {
  //   Window1 mainWindow = new Window1();
  //   mainWindow.Show();
  // }
  private void MainExHandler(object sender, DispatcherUnhandledExceptionEventArgs e)
  {
    MessageBox.Show("Exception aufgetreten: " + e.Exception.Message);
    e.Handled = true;
  }
}
Listing 4.5: Beispiele\Kap04\AnwendungsEreignisseProj\MyApp.xaml.cs

<Window ...>
  <Grid>
    <Button Click="ErzeugeException">Exception auslösen</Button>
  </Grid>
</Window>
Listing 4.6: Beispiele\Kap04\AnwendungsEreignisseProj\Window1.xaml

private void ErzeugeException(object sender, RoutedEventArgs e)
{
  throw new Exception("Kabumm");
}
Listing 4.7: Beispiele\Kap04\AnwendungsEreignisseProj\Window1.xaml.cs

4.3.2 Fensterereignisse
Zur Initialisierung einer Anwendung und für zahlreiche weitere Situationen besitzt auch ein Fenster vom Typ Window einige interessante Ereignisse. Insbesondere kann für die Initialisierung der Oberfläche auf das Ereignis Loaded zurückgegriffen werden. Hier können z.B. noch weitere Komponenten dynamisch der Oberfläche hinzugefügt werden oder Sie können vorhandene Komponenten konfigurieren. Die folgende Tabelle listet einige Ereignisse der Klasse Window auf.

EreignisBeschreibung
ActivatedDas Fenster gelangt in den Vordergrund.
ClosedDas Fenster wird geschlossen.
ClosingWird aufgerufen wenn das Fenster geschlossen werden soll. Dies kann über die Eigenschaft Cancel des Parameters vom Typ CancelEventArgs aber verhindert werden. Weisen Sie ihm dazu den Wert true zu.
DeactivatedDas Fenster wurde deaktiviert, d.h. ein anderes Fenster wurde ausgewählt.
InitializedDirekt nach der Erzeugung des Objekts durch den Konstruktor wird dieses Ereignis ausgelöst. Es sollten hier allerdings noch keine Zugriffe auf die Werte der Komponenten durchgeführt werden, da diese zum Teil noch unbestimmt sein können.
LoadedBringen Sie hier den Code unter, der direkt nach dem Initialisieren aller Elemente eines Fensters ausgeführt werden soll. Das Ereignis Initialized ist dazu meist ungeeignet, da das Layout der Komponenten noch nicht abgeschlossen ist.
LocationChangedDie Position des Fensters hat sich geändert.
SizeChangedDie Größe des Fensters hat sich geändert.
StateChangedDer Fensterstatus (minimiert, maximiert, normal) hat sich geändert.
UnloadedDas Fenster ist zerstört. Ein Zugriff auf die Elemente ist nicht mehr möglich.

Tabelle 4.2: Einige Ereignisse der Klasse Window

4.3.3 Ereignishandler dynamisch zuweisen
Eine Verknüpfung einer Komponente die innerhalb von XAML oder im Code erzeugt wurde mit einem Ereignishandler, kann auch dynamisch zur Laufzeit einer Anwendung erfolgen. Dazu stellen Sie einen Ereignishandler mit den geforderten Parametern bereit und fügen sie dem entsprechenden Ereignis hinzu. Die Vorgehensweise entspricht damit genau der Vorgehensweise wie in Windows Forms-Anwendungen.

BEISPIEL: DynamischeEreignisseProj.zip - Download

Die Anwendung verfügt über zwei Schaltflächen, von denen eine bereits mit einem Click-Ereignishandler verknüpft ist. Die andere Schaltfläche verfügt über den Namen BtnEreignis, unter dem Sie später angesprochen werden kann. In der Methode ErzeugeVerknuepfung() wird ein neuer Delegate vom Typ RoutedEventHandler erzeugt, dem als Parameter ein Verweis auf die Methode übergeben wird, welche die Ereignisbehandlung implementiert. Dieser EreignisHandler wird dem Click-Ereignis hinzugefügt. Danach wird die Beschriftung der Schaltfläche nach Jetzt verknüpft geändert. Die Methode BtnEreignisClick() besitzt wieder den Standardaufbau der Click-Ereignisbehandlung und zeigt eine MessageBox an.

<StackPanel>
  <Button Click="ErzeugeVerknuepfung">
    Verknüpfung herstellen
  </Button>
  <Button Name="BtnEreignis">Keine Verknüpfung</Button>
</StackPanel>
Listing 4.8: Beispiele\Kap04\DynamischeEreignisseProj\Window1.xaml

private void ErzeugeVerknuepfung(object sender, RoutedEventArgs e)
{
  BtnEreignis.Click += new RoutedEventHandler(BtnEreignisClick);
  BtnEreignis.Content = "Jetzt verknüpft";
}
private void BtnEreignisClick(object sender, RoutedEventArgs e)
{
  MessageBox.Show("Klappt");
}
Listing 4.9: Beispiele\Kap04\DynamischeEreignisseProj\Window1.xaml.cs

nach oben

4.4 Ereignisweiterleitung

Wenn z.B. auf einen Button geklickt wird, wird ein Ereignis ausgelöst. Dieses Ereignis könnte nun direkt vom Button verarbeitet werden, danach wäre es nicht mehr verfügbar. Die WPF erlaubt es aber, dass ein Button weitere untergeordnete Elemente besitzt. Ein Klick auf solch einen Button kann auf dem Button (z.B. dem Randbereich), aber auch auf ein untergeordnetes Element (z.B. ein Bild und eine Beschriftung) erfolgen. Die Frage die sich nun für den Entwickler stellt ist, wie kann er auf alle Möglichkeiten reagieren, wenn auf den Button geklickt wird. Eine Lösung wäre, den Klick an die untergeordneten Elemente weiterzureichen. Genau dafür sind die so genannten RoutedEvents der WPF da. Sie haben die Parameter vom Typ RoutedEventArgs bereits mehrfach in den Ereignishandlern verwendet.

Bei einem RoutedEvent wird ein Klick entweder an das jeweils untergeordnete oder übergeordnete Element weitergereicht oder es wird wie bisher direkt am auftreffenden Element konsumiert. Die WPF verwendet drei verschiedene Mechanismen zum Event Routing, d.h. auf welchem Wege Events durch den Elementbaum durchgereicht werden.

Die Abbildung 4.2 zeigt die Ereignisweiterleitung am Beispiel eines Klicks auf einen Button. Das erste Ereignis, das ausgelöst wird, ist ein getunneltes Ereignis, welches beim Wurzelelement des Fensters startet. Danach wird es an den Layoutcontainer weitergegeben und zuletzt an den Button. Vom Button startet dagegen ein Bubbled Event. Wie Wasserblasen blubbert es zunächst zum StackPanel und von dort zum Fenster, dem Wurzelelement. Die TextBox bekommt von alldem nichts mit, da sie sich nicht innerhalb des Elementbaums zwischen Button und Wurzelelement befindet. Routed Events sind demnach eine Spezialform von Ereignissen in der WPF. Es werden hier alle Handler aufgerufen, die sich bei dem Ereignis registriert haben und zwischen dem Element bei dem das Ereignis aufgetreten ist und der Wurzel liegen.

Ereignisweiterleitung

Abbildung 4.2: Ereignisweiterleitung

Die meisten RoutedEvents haben auch ein korrespondierendes Preview-Event. So hat z.B. ein gebubbeltes MouseLeftButtonDown-Ereignis ein korrespondierendes getunneltes Ereignis PreviewMouseLeftButtonDown. Beide basieren auf demselben Ereignistyp MouseButtonEventHandler. Einige spezielle Ereignisse sind vom Typ Direct wie z.B. die Ereignisse MouseEnter und MouseLeave. Direkte Ereignisse werden direkt an der auftretenden Komponente verarbeitet (oder auch nicht). Sie werden weder gebubbelt noch getunnelt. Bei den beiden hier erwähnten Ereignissen wäre das auch nicht sonderlich sinnvoll, denn das Ereignis MouseEnter soll ja gerade dann eintreten, wenn die Maus eine ganz bestimmte Komponente beginnt zu überfahren.

Erwähnenswert ist auch das Ereignis Click. Dieses Ereignis wird in der Klasse ButtonBase bereitgestellt und basiert auf den Ereignissen MouseLeftButtonDown und MouseLeftButtonUp bzw. wird es in diesem Ereignissen ausgelöst. Es ist ein Bubbled Event und besitzt kein äquivalentes getunneltes Event. Da es in den Ereignissen MouseLeftButtonDown und MouseLeftButtonUp ausgelöst wird und diese eigentlich ersetzen soll, wird es als behandelt markiert. Das heißt, die beiden Ereignisse MouseLeftButtonDown und MouseLeftButtonUp werden nicht bis zur Wurzel "durchgebubbelt".

Um Festzustellen, von welchem Typ ein Ereignis ist, verwenden Sie die MSDN-Hilfe. Im Abschnitt Routed Event Information wird der Ereignistyp, die Routing Strategie sowie der Typ des Delegates angegeben. Gibt es ein korrespondierendes Ereignis, wird dies unter den Standardinformationen ebenfalls angegeben. Kann außerdem eine Methode überschrieben werden, die beim Auftreten des Ereignisses aufgerufen wird, steht diese ebenfalls hier.

Informationen zum Ereignistyp ermitteln

Abbildung 4.3: Informationen zum Ereignistyp ermitteln

BEISPIEL: RoutedEventsProj.zip - Download

Zur Veranschaulichung der Ereignisweiterleitung verschachtelt das Beispiel einige Komponenten. Insbesondere besteht der oben angedeutete Button aus verschiedenen Komponenten. Wird nun beispielsweise auf das Label mit dem Text Hallo geklickt, ergibt sich ein recht umfangreicher Ereignisfluss. Die Komponenten werden mit jeweils einem Bubbled- und einem Tunneled-Ereignis verknüpft. Außerdem werden das Window-, das äußere StackPanel- sowie das Button-Element noch mit dem Ereignis Click verknüpft.

Im Ergebnis sehen Sie nach einem Klick auf den Text Hallo, die Ereignisfolge aus Abbildung 4.3. Insbesondere fällt dabei auf, dass plötzlich nach den MouseDown-Ereignissen Click-Ereignisse auftreten. Dies ist genau die Stelle, an der der Button das Klicken mit der linken Maustaste in ein Click-Ereignis umwandelt und das originale Ereignis als behandelt (Handled=true) markiert.

Protokollierter Ereignisfluss

Abbildung 4.4: Protokollierter Ereignisfluss

<Window ... MouseDown="RoutedClick" PreviewMouseDown="TunnelClick"
            ButtonBase.Click="RoutedClick">
  <StackPanel Name="StackPanelAussen" MouseDown="RoutedClick"
              PreviewMouseDown="TunnelClick" ButtonBase.Click="RoutedClick">
    <Button Click="RoutedClick" MouseDown="RoutedClick"
            PreviewMouseDown="TunnelClick" Name="ButtonAussen">
      <StackPanel Orientation="Horizontal"
                  Name="StackPanelMitte" MouseDown="RoutedClick"
                  PreviewMouseDown="TunnelClick">
        <Border Background="LightBlue" BorderBrush="Black"
                BorderThickness="2" Name="Border" MouseDown="RoutedClick"
                PreviewMouseDown="TunnelClick">
          <StackPanel Orientation="Horizontal" Name="StackPanelInnen"
                      MouseDown="RoutedClick" PreviewMouseDown="TunnelClick">
            <Label Name="Label" MouseDown="RoutedClick"
                   PreviewMouseDown="TunnelClick">Hallo</Label>
          </StackPanel>
        </Border>
      </StackPanel>
    </Button>
    <TextBox>Hallo</TextBox>
    <ListBox Name="EventLB" Height="250"/>
  </StackPanel>
</Window>
Listing 4.10: Beispiele\Kap04\RoutedEventsProj\Window1.xaml

private void RoutedClick(object sender, RoutedEventArgs e)
{
  EventLB.Items.Add(e.RoutedEvent.ToString() + "> " + sender.GetType().ToString() +
                    ", " + (sender as FrameworkElement).Name);
  e.Handled = false;
}
private void TunnelClick(object sender, MouseButtonEventArgs e)
{
  EventLB.Items.Add(e.RoutedEvent.ToString() + "> " + sender.GetType().ToString() +
                    ", " + (sender as FrameworkElement).Name);
  e.Handled = false;
}
Listing 4.11: Beispiele\Kap04\RoutedEventsProj\Window1.xaml.cs

nach oben

4.5 Ereignismethoden

Über die Klasse UIElement werden für die meisten Ereignisse virtuelle Methoden bereitgestellt, die zur Behandlung des Ereignisses einfach überschrieben werden müssen. Dazu muss allerdings eine neue Klasse von der betreffenden Elementklasse abgeleitet werden, um diese Möglichkeit zu nutzen. Im Falle eines Fensters oder der gesamten Anwendung ist das kein Problem, da für beide sowieso neue Klassen erzeugt werden (siehe Beispiel im Abschnitt Anwendungsereignisse). Wollen Sie allerdings eine solche Methode für einen Button überschreiben, müssen Sie eine neue Klasse erzeugen, die von Button abgeleitet ist.

Beim Überschreiben der Methoden muss darauf geachtet werden, dass sie mit protected override gekennzeichnet werden. Für das Ereignis Click müsste z.B. die Methode OnClick() überschrieben werden.

BEISPIEL: EreignisMethodenProj.zip - Download

Im Beispiel wird ein neuer Button mit dem Namen NewButton deklariert. Dieser wird dann innerhalb einer XAML-Datei genutzt. Dazu muss der Namespace des Buttons in der XAML-Datei eingebunden werden. Über den darüber neu definierten Alias frischa wird später der Button referenziert. Im neuen Button wird nur die Methode OnClick() überschrieben und darin eine MessageBox angezeigt.

<Window ...
        xmlns:frischa="clr-namespace:EreignisMethodenProj"
        Title="EreignisMethodenProj" Height="300" Width="300">
  <StackPanel>
    <frischa:NewButton>Klick mich</frischa:NewButton>
  </StackPanel>
</Window>
Listing 4.20: Beispiele\Kap04\EreignisMethodenProj\Window1.xaml

using System;
using System.Windows.Controls;
using System.Windows;
namespace EreignisMethodenProj
{
  class NewButton: Button
  {
    protected override void OnClick()
    {
      base.OnClick();
      MessageBox.Show("Klick");
    }
  }
}
Listing 4.21: Beispiele\Kap04\EreignisMethodenProj\NewButton.cs

nach oben