23.3 Logische Ressourcen
Im letzten Abschnitt haben Sie gelernt, wo in einer WPF-Anwendung überall Ressourcen definiert werden können. Nun ist es Zeit, sich mit dem Thema zu beschäftigen, wie eine Ressource definiert wird. Wie Sie im weiteren Verlauf dieses und der folgenden Kapitel noch sehen werden, können Ressourcen sehr vielfältig geprägt sein. Das geht weit darüber hinaus, nur simple Hintergrundfarben einer Anwendung zu verallgemeinern. Tatsächlich ist der Einsatzbereich so weit gesteckt, dass in den Resources-Abschnitten ein Großteil des XAML-Codes zu finden ist.
Zunächst einmal wollen wir uns den als »logische Ressourcen« bezeichneten Ressourcen widmen. Logische Ressourcen sind Objekte, die als Ressourcen definiert sind und innerhalb der Anwendung referenziert werden können.
Logische Ressourcen lassen sich mit Cascading Style Sheets (CSS) vergleichen, die ebenfalls zentral außerhalb ihres Einsatzgebietes definiert werden. Da Sie beliebig viele Ressourcen festlegen können, muss jede Ressource über einen eindeutigen Schlüssel identifizierbar sein. Dieser wird mit dem Key-Attribut beschrieben, das einem Namespace zugeordnet ist, der per Vorgabe durch x: beschrieben wird.
<Window ...>
<Window.Resources>
<LinearGradientBrush x:Key="color">
<GradientStop Color="Black" Offset="0.0" />
<GradientStop Color="White" Offset="1.0" />
</LinearGradientBrush>
</Window.Resources>
[...]
</Window>
Listing 23.5 Definition einer logischen Ressource
Elemente, die Ressourcen referenzieren, verwenden zwei Markup-Erweiterungen:
- StaticResource
- DynamicResource
Beispielsweise könnte man eine Schaltfläche mit der in Listing definierten Ressource wie folgt verbinden:
<Button Background="{StaticResource color}">Button 1</Button>
23.3.1 Statische Ressourcen
Der Name StaticResource suggeriert im ersten Moment, dass hiermit eine Ressource statisch eingebunden wird. Das ist auch richtig, aber es stellt sich die Frage, was genau unter statisch zu verstehen ist. Zur Beantwortung hilft das folgende Beispielprogramm. In diesem ist eine Ressource im Abschnitt Window.Resources definiert, die der Einfachheit halber nur ein SolidColorBrush-Objekt beschreibt. Die Ressource wird von einem Button mit StaticResource referenziert. Zwei weitere Schaltflächen dienen dazu, die Ressource auf unterschiedliche Art und Weise zu ändern.
// Beispiel: ..\Kapitel 23\StaticResourceSample
<Window ...>
<Window.Resources>
<SolidColorBrush x:Key="btnBackground" Color="Yellow" />
</Window.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Button Name="button1" Margin="10" VerticalAlignment="Top" Height="40"
Background="{StaticResource btnBackground}">Button 1</Button>
<StackPanel Grid.Column="1">
<Button Name="btnChangeColor1" Margin="5"
Click="btnChangeColor1_Click">Ressource ändern</Button>
<Button Name="btnChangeColor2" Margin="5,0,5,5"
Click="btnChangeColor2_Click">Ressource erstellen</Button>
</StackPanel>
</Grid>
</Window>
Listing 23.6 XAML-Code des Beispielprogramms »StaticResourceSample«
Zu diesem XAML-Code gehören zwei Ereignishandler, die in der Code-Behind-Datei codiert sind.
private void btnChangeColor1_Click(object sender, RoutedEventArgs e) {
SolidColorBrush brush = (SolidColorBrush)FindResource("btnBackground");
brush.Color = Colors.Red;
}
private void btnChangeColor2_Click(object sender, RoutedEventArgs e) {
SolidColorBrush brush = new
SolidColorBrush(Colors.Green);
this.Resources["btnBackground"] = brush;
}
Listing 23.7 Code der beiden Ereignishandler
Im Ereignishandler der Schaltfläche btnChangeColor1 wird mit FindResource die Referenz auf die Ressource per Schlüssel gesucht und anschließend die Farbe geändert. Im zweiten Ereignishandler wird ein neues Objekt vom Typ SolidColorBrush erzeugt und danach an die existente Ressource btnBackground übergeben.
Der Zugriff auf Ressourcen mit Programmcode (hier mit FindResource) war bisher noch nicht das Thema. Dennoch sollte der C#-Code verständlich sein.
Sie werden feststellen, dass die ressourcengebundene Schaltfläche die Hintergrundfarbe ändert, wenn Sie auf die obere Schaltfläche klicken. Klicken Sie auf die untere, passiert nichts – auch keine Ausnahme. Das ist mit der Verhaltensweise von StaticResource zu erklären. Die Eigenschaft, hier Background der bindenden Komponente, wird an ein Objekt der Ressource SolidColorBrush gebunden. Die Änderung einer Eigenschaft der Ressource (hier Color) wird von der bindenden Komponente erkannt und umgesetzt. Das Klicken der unteren Schaltfläche hingegen hat keine Auswirkungen auf die bindende Komponente, weil im Programmcode ein neues Objekt erzeugt wird.
Zusammenfassend muss man also feststellen, dass StaticResource immer dasselbe Ressourcenobjekt referenziert, aber durchaus in der Lage ist, dessen Eigenschaftsänderungen umzusetzen.
Ein Sonderfall: Elemente als Ressourcen
Als Ressource kann prinzipiell jedes beliebige Objekt verwendet werden. Eine Ressource kann per Vorgabe auch nur einmal instanziiert werden. Jeder Zugriff, ob im XAML- oder im C#-Programmcode, ist ein Zugriff auf dieselbe Instanz.
Diese Charakteristik der Ressourcen führt uns in eine Situation, die wir an dieser Stelle betrachten müssen. Bisher haben wir nur mit einem einfachen Brush-Objekt die Ressource beschrieben. Es ist aber auch denkbar, ein Steuerelement in einer Ressource zu beschreiben. Im nächsten Beispiel wird dazu ein Image genommen, das ein Bildchen beschreibt.
// Beispiel: ..\Kapitel 23\StaticResourceWithControl
<Window ...>
<Window.Resources>
<Image x:Key="image" Source="smiley.jpg" />
</Window.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Button Grid.Column="0" Content="{StaticResource image}" />
<Button Grid.Column="1" Content="{StaticResource image}"/>
</Grid>
</Window>
Listing 23.8 XAML-Code des Beispielprogramms »StaticResourceWithControl«
Nach dem Start der Anwendung werden Sie feststellen, dass die Ressource nur einmal genutzt wird (siehe Abbildung 23.2).
Abbildung 23.2 Ausgabe des Beispielprogramms »StaticResourceWithControl«
Der Grund ist, dass ein Image Teil des Elementbaums ist und nur einmal verwendet werden kann. Die Brush-Objekte, die wir vorher als Ressourcen definiert hatten, sind nicht Teil des Elementbaums und konnten daher von mehreren Elementen verwendet werden.
Eine Lösung des Problems wird natürlich von der WPF angeboten. Per Vorgabe werden die Ressourcenobjekte nur einmal instanziiert. Jedes Element, das dieselbe Ressource referenziert, erhält dieselbe Referenz. Wird die Ressource jedoch mit dem Attribut x:Shared=false erweitert, wird die Ressource bei jedem Zugriff instanziiert. Die Standardeinstellung des Attributs ist demzufolge true. Ändern Sie also die Ressource wie im folgenden Listing ab:
<Image x:Key="image" Source="smiley.jpg" x:Shared="false" />
Listing 23.9 Ressource mehrfach instanziieren
Danach wird die Anwendung die Ressource so benutzen, wie Sie es sich vorgestellt haben (siehe Abbildung 23.3).
Abbildung 23.3 Ausgabe des Beispielprogramms »StaticResourceWithControl« nach der Änderung
23.3.2 Dynamische Ressourcen
Ganz anders als statische Ressourcen verhalten sich Ressourcen, die mit DynamicResource eingebunden werden. Eine Änderung der Referenz der Ressource wird dazu führen, dass die bindende Komponente die neue Ressource zur Kenntnis nimmt und darauf entsprechend reagiert. Sie können das sehr leicht ausprobieren, wenn Sie im Beispielprogramm StaticResourceSample die Bindung mit StaticResource gegen DynamicResource austauschen, also:
[...]
<Button Name="button1" Margin="10" VerticalAlignment="Top" Height="40"
Background="{DynamicResource btnBackground}">Button 1</Button>
[...]
Listing 23.10 Änderung im Beispielprogramm »StaticResourceSample«
Jetzt werden beide Schaltflächen genau die Verhaltensweise zeigen, die von ihnen erwartet wird. Weiterhin wird die Änderung der Eigenschaft Color der Schaltfläche btnChangeColor1 dazu führen, dass die bindende Schaltfläche sich aktualisiert, aber auch das Anklicken der unteren Schaltfläche btnChangeColor2 führt zu dem erwarteten Erfolg.
Es mag im ersten Moment verlockend sein, immer mit DynamicResource an Ressourcen zu binden. Sie sollten sich das aber genau überlegen, da jeder dynamische Vorgang immer zu Lasten der Performance geht. Das gilt selbstverständlich auch für DynamicResource. Wenn Sie davon ausgehen können, dass sich die Ressource nicht ändert, sollten Sie daher immer mit StaticResource binden.
23.3.3 Ressourcen mit C#-Code bearbeiten
Alles, was in XAML möglich ist, kann auch mit Programmcode erreicht werden. Dazu zählt auch die Suche nach einer Ressource und deren Anbindung an eine Eigenschaft. Im Beispielprogramm StaticResourceSample wurde bereits eine Ressource mit Code geändert. Es ist nun an der Zeit, hierzu die entsprechenden Hintergründe zu beleuchten.
Zuweisung einer statischen Ressource
Am einfachsten gestaltet sich der Zugriff auf eine statische Ressource, wenn sich die Ressource im aktuellen Fenster befindet und sie namentlich bekannt ist. Mit der Eigenschaft Resources rufen Sie die Ressource ab und geben dabei den Namen der gesuchten Ressource an. Angenommen, die Ressource background sei unter Window.Resources definiert, würde die Anweisung wie folgt lauten:
button1.Background = (Brush)Resources["background"];
Listing 23.11 Ressource innerhalb des Fensters mit »Resources« lokalisieren
Die gefundene Ressource muss noch in den entsprechenden Typ konvertiert werden, weil jeder Ressourceneintrag vom Typ Object ist. Da die Hintergrundfarbe einer Schaltfläche vom Typ Brush ist, erfolgt die Konvertierung in der vorangehenden Anweisung in genau diesen Typ.
Befindet sich die Ressource nicht im aktuellen Fenster, eignet sich die gezeigte Anweisung nicht. Stattdessen muss die Ressource in der gesamten Hierarchie gesucht werden. Hierzu eignet sich die Methode FindResource, die jedes Steuerelement hat. Sie rufen die Methode auf das Objekt auf, dem die Ressource zugewiesen werden soll, und übergeben als Argument den Bezeichner der Ressource. Die gefundene Ressource muss natürlich ebenfalls in den Datentyp der entsprechenden Eigenschaft konvertiert werden.
button1.Background = (Brush)button1.FindResource("background");
Listing 23.12 Ressource mit »FindResource« lokalisieren
Wird die angegebene Ressource nicht gefunden, ist eine Exception die Folge. Daher ist es in vielen Fällen vorteilhaft, alternativ die Methode TryFindResource zu benutzen. Im Gegensatz zu FindResource liefert TryFindResource eine null-Referenz, falls die Ressource nicht gefunden wird.
Zuweisung einer dynamischen Ressource
Mit den Methoden FindResource und TryFindResource wird die Markup-Erweiterung StaticResource mittels Code beschrieben. Eine Ressource dynamisch zu binden, ist mit diesen Methoden nicht möglich. Zur Anbindung an eine dynamische Ressource dient die Methode SetResourceReference. Die Methode hat zwei Parameter. Dem ersten wird die Abhängigkeitseigenschaft übergeben, die an die Ressource dynamisch gebunden werden soll, dem zweiten Parameter der Ressourcenbezeichner.
button1.SetResourceReference(Button.BackgroundProperty, "background");
Listing 23.13 Dynamische Ressource mit »SetResourceReference« binden
Beachten Sie hierbei, dass die Angabe einer Abhängigkeitseigenschaft (Dependency Property) erfordert, diese direkt zu benennen, in unserem Fall demnach Button.BackgroundProperty.
23.3.4 Abrufen von Systemressourcen
Bisher haben wir nur benutzerdefinierte Ressourcen verwendet. Sie können aber auch auf Ressourcen zugreifen, die vom System bereitgestellt werden. WPF enthält im Namespace System.Windows drei Klassen, mit denen sich bestimmte Eigenschaften des Systems auswerten lassen:
SystemFonts beschreibt Eigenschaften, die die Systemressourcen für Schriftarten verfügbar machen, SystemColors beschreibt die vom System verwendeten Farben, und SystemParameters enthält Eigenschaften, die Sie zum Abfragen von Systemeinstellungen verwenden können. Die drei Klassen enthalten in ihren Eigenschaften immer die aktuellen Werte des Betriebssystems, die von den Einstellungen in der Systemsteuerung abhängen.
Wenn Sie sich die Dokumentation dieser Klassen ansehen, werden Sie feststellen, dass für jede Systemeigenschaft zwei Eigenschaften definiert sind, zum Beispiel
- CaptionHeight
- CaptionHeightKey
in der Klasse SystemParameters. Doch wozu braucht man zwei ähnliche Eigenschaften?
CaptionHeight ist vom Typ double und gibt die Höhe der Titelleiste in Pixeln an. CaptionHeightKey hingegen ist vom Typ ResourceKey. Darüber wird der Name der Systemressource gekennzeichnet, die den Wert der Eigenschaft zurückliefert.
Möchten Sie auf eine Ressource statisch zugreifen, reicht die Angabe der Eigenschaft ohne das Suffix Key vollkommen aus. In diesem Fall reagiert die Anwendung nicht auf Änderungen an den Systemeinstellungen. Sehen wir uns eine statische Ressourcenabfrage an. Beachten Sie, dass für den Zugriff auf die Systemressourcen die Markup-Erweiterung x:Static vorgeschrieben ist.
<Label Content="{StaticResource {x:Static SystemParameters.CaptionHeightKey}}" />
Die Angabe der Markup-Erweiterung StaticResource bewirkt, dass eine Suche nach der Ressource entlang der Hierarchie angestoßen wird. Das geht zu Lasten der Performance.
Mit der XAML-Markup-Erweiterung x:Static greift man auf die statischen Eigenschaften, Felder oder Konstanten einer Klasse oder Aufzählung zu.
Besser ist es, direkt auf den Wert der Eigenschaft mit
<Label Content="{x:Static SystemParameters.CaptionHeight}" />
zuzugreifen. Im C#-Code können Sie den Wert der Eigenschaft mit
double height = SystemParameters.CaptionHeight;
ermitteln.
Bei einer Änderung des Werts in der Systemsteuerung nimmt die Anwendung zur Laufzeit keine Notiz von der Änderung. Soll sich die Anwendung der Änderung anpassen, müssen Sie die Ressource mit DynamicResource einbinden.
<Label Content="{DynamicResource {x:Static SystemParameters.CaptionHeightKey}}"/>
Systemressourcen anpassen
Wie weiter oben schon erläutert, läuft die Suche nach einer bestimmten Ressource nach einem vorgegebenen Schema ab. Sie beginnt im Logical Tree bei dem Element, auf dem die Markup-Erweiterung StaticResource oder DynamicResource verwendet oder die Methode FindResource aufgerufen wird. Wird die Ressource nicht gefunden, wird im Application-Objekt danach gesucht. Die letzte Ebene der Suche bilden die Systemressourcen, in denen sich die Einstellungen des Betriebssystems befinden.
Dieser Suchprozess gestattet, eine »höher liegende« Ressource durch eine tiefer liegende zu überschreiben, denn sobald die Suche erfolgreich war, wird sie beendet. Folglich lassen sich auch die vorgegebenen Systemressourcen sehr einfach durch anwendungsspezifische ersetzen.
Damit ist es beispielsweise sehr einfach, die Hintergrundfarbe aller Fenster einer Anwendung festzulegen. Verantwortlich dafür ist die Ressource WindowBrushKey in der Klasse SystemColors. Wünschen Sie einen roten Hintergrund bei allen Fenstern der Anwendung, ergänzen Sie den Resource-Abschnitt der Datei App.xaml einfach wie folgt:
<Application.Resources>
<SolidColorBrush Color="Red" x:Key="{x:Static SystemColors.WindowBrushKey}" />
</Application.Resources>
Einen Farbverlauf festzuschreiben ist mit der Ressource WindowBrushKey nicht möglich, da der durch diese Ressource beschriebene Wert vom Typ SolidColorBrush ist.
Ihre Meinung
Wie hat Ihnen das Openbook gefallen? Wir freuen uns immer über Ihre Rückmeldung. Schreiben Sie uns gerne Ihr Feedback als E-Mail an kommunikation@rheinwerk-verlag.de.