Ein Sache die eigentlich offensichtlich ist, hat mich heute überraschend getroffen:
Properties die man innerhalb eines Silverlight Custom Control per <setter> setzt, sind statisch und alle Instanzen dieses Controls teilen sich die gleiche Instanz für den Property-Inhalt.
Nach kurzem suchen in der MSDN wurde deutlich dass das auch das erwartete Verhalten ist:
"Contains property setters that can be shared between instances of a type." [1]
Folgendes Szenario:
Ich wollte ein ContontControl schreiben, das ein Control enthält und eine IsDirty-Property bereitstellt. Wird IsDirty auf true gesetzt, soll sich das enthaltene Control entsprechend ändern, z.B. indem der Text jetzt in rot dargestellt wird. Es sollte die Möglichkeit bestehen dass eine TextBox im Dirty-State anders aussieht als z.B. eine Textbox.
Zu diesem Zewck habe ich eine kleine ResourceSelector Komponente geschrieben, die aufgrund des Typnamens des enthaltenen Controls ein Storyboard auswählt und dieses auf das enthaltene Control anwendet. Auf diese Weise kann man für jedes Control einen eigenen Dirty-Style definieren.
Meinem DirtyContainer-Control habe ich dann eine Property StoryboardSelector spendiert, die eine Instanz von meinem ResourceSelector aufnehmen kann.
Das führt zu folgendem Markup:
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vsm="clr-namespace:System.Windows;assembly=System.Windows"
xmlns:Local="clr-namespace:Silverlight.Controls"
>
<Style TargetType="Local:DirtyContainer">
<Setter Property="StoryboardSelector">
<Setter.Value>
<Local:ResourceSelector>
<Local:ResourceSelector.Resources>
<ResourceDictionary>
<Storyboard x:Key="TextBox" >
...
</Storyboard>
<Storyboard x:Key="ComboBox">
...
</Storyboard>
<Storyboard x:Key="CheckBox">
...
</Storyboard>
</ResourceDictionary>
</Local:ResourceSelector.Resources>
</Local:ResourceSelector>
</Setter.Value>
</Setter>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Local:DirtyContainer">
<Grid>
<ContentControl x:Name="Content" Content="{TemplateBinding Content}" ContentTemplate="{TemplateBinding ContentTemplate}"
HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
HorizontalContentAlignment="Stretch" VerticalContentAlignment="Stretch"
Padding="1">
</ContentControl>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
Wenn sich nun die IsDirty-Eigenschaft von meinem DirtyContainer ändert, suche ich in dem StoryboardSelector nach einem passenden Storyboard basierend auf dem Typnamen (m_Content ist die Instanz des ContentControls aus dem Template):
StoryboardSelector.ResourceSwitch = m_Content.Content.GetType().Name;
DirtyStoryboard = StoryboardSelector.CurrentResource as Storyboard;
Storyboard.SetTarget(DirtyStoryboard, m_Content.Content as DependencyObject);
DirtyStoryboard.Begin();
Geknallt hat es dann in Zeile 3, wenn man zweimal eine Combobox in der Applikation verändert hat:
Der Vorgang ist auf einer aktiven Animation oder einem aktiven Storyboard nicht gültig.
Das Stammstoryboard muss zunächst angehalten werden.
Diese Fehlermeldung ist dem Umstand geschuldet, dass jetzt alle Comboboxen das gleiche Storyboard verwenden, aber eine Storyboard-Instanz immer nur ein Target haben darf.
So weit, so gut, das verstehe ich alles. Aber ich suche eine Lösung bei der ich bereits im Control-Style einen Standard für die Storyboards setzen kann. Derzeit habe ich den ganzen ResourceSelector in die Resources-Auflistung des ContentControls geschoben und hole mir die Instanz im OnApplyTemplate:
StoryboardSelector = m_Content.Resources["ResourceSelector"] as ResourceSelector;
Keine wirklich tolle Lösung - für bessere Ideen bin ich jederzeit offen...