Laden...

ResourceDictionary an Property binden - geht das?

Erstellt von DeSharper vor 7 Jahren Letzter Beitrag vor 7 Jahren 3.080 Views
D
DeSharper Themenstarter:in
40 Beiträge seit 2016
vor 7 Jahren
ResourceDictionary an Property binden - geht das?

Immer wenn ich denke ich hätte eine gute Idee, hat Visual Studio irgendwas dagegen.
In diesem Fall dachte ich, es wäre eine gute Idee, meine StylingResourcen sauber zu binden, anstatt bei jeder Änderung im Code behind rumwurschteln zu müssen.


<Window.Resources>
    <ResourceDictionary x:Name="CurrentResourceDictionary" >
        <converter:ThemeToResourceFileConverter x:Key="ThemeConverter"/>
        <ResourceDictionary.MergedDictionaries>
            <ResourceDictionary Source="{Binding Path=CurrentTheme, 
                                                                      Converter={StaticResource ThemeConverter}}"/>
        </ResourceDictionary.MergedDictionaries>
    </ResourceDictionary>
</Window.Resources>

Den Converter dazu poste ich nicht, weil er gar nicht durchlaufen wird bevor der Fehler auftaucht.

Obwohl folgender Fehler im xaml erkannt wird kompiliert alles und das Programm startet.
"Value cannot be null. Parameter name: item"

Dann bekomme ich eine Exception:
XamlParseException occured
Exception thrown: 'System.Windows.Markup.XamlParseException' in PresentationFramework.dll
Additional information: Zeilennummer "15" und Zeilenposition "37" von "Die Angabe eines Werts für "System.Windows.StaticResourceExtension" führte zu einer Ausnahme.".

Ich hab nach beiden Fehlermeldungen schon gegoogelt, allerdings mit mäßigem Erfolg. Keiner mit der selben Meldung hat da drin irgendwas gebindet. Aber wenn ich die Source ohne Binding einfach fest zuweise, funktioniert alles.

Daher meine Frage: Geht das was ich vorhabe überhaupt, wenn ja, geht es so wie ich mir das vorstelle? Wenn nein, gibt es eine andere saubere Lösung?

5.658 Beiträge seit 2006
vor 7 Jahren

Hi DeSharper,

hast du denn überhaupt eine Resource mit einer Instanz des Converters angelegt? Irgendwo mußt du etwas stehen haben wie:

<converters:ThemeConverter x:Key="ThemeConverter" />

Dann kannst du via StaticResource darauf zugreifen.

Ansonsten ist evtl. die InnerException etwas aufschlußreicher.

Weeks of programming can save you hours of planning

2.079 Beiträge seit 2012
vor 7 Jahren

@MrSparkle:

Die Ressource gibt es 😉

<Window.Resources>
    <ResourceDictionary x:Name="CurrentResourceDictionary" >
        <!-- Hier: -->
        <converter:ThemeToResourceFileConverter x:Key="ThemeConverter"/>

        <ResourceDictionary.MergedDictionaries>
            <ResourceDictionary Source="{Binding Path=CurrentTheme,
                                                                      Converter={StaticResource ThemeConverter}}"/>
        </ResourceDictionary.MergedDictionaries>
    </ResourceDictionary>
</Window.Resources>

An sich sollte das funktionieren.
Ich würde prüfen ob es im (statischen) Konstruktor einen Fehler gibt. Auch bei (statischen) Klassen-Variablen oder Properties, die direkt zugewiesen werden, schauen.
WPF fängt solche Exceptions und wrapt sie in Eigene.

Ansonsten wüsste ich auch nicht, was es anderes sein soll außer eine nicht vorhandene Ressource.
Wichtig ist dabei übrigens auch, dass der ThemeConverter im ResourceDictionary vorher steht. WPF geht von oben nach unten durch und arbeitet die Resourcen ab.

PS:
Wo ich den letzten Absatz geschrieben habe, kam mir noch ein Gedanke:
Was ist, wenn der Inhalt von MergedDictionaries aus irgendeinem Grund noch vor den eigentlichen Inhalten gefüllt wird?
Dann würde das Binding aufgerufen werden bevor der Converter erstellt wurde, was zu der Exception führt.

Schau daher mal, ob Du den ThemeConverter in der Hierarchie irgendwo weiter oben in den Ressourcen ablegen kannst.
Entweder Du verlagerst den Part mit den MergedDictionaries in die Ressourcen von irgendeinem Container im Window, oder Du legst den ThemeConverter in die App.xaml

NuGet Packages im Code auslesen
lock Alternative für async/await

Beim CleanCode zählen nicht die Regeln, sondern dass wir uns mit diesen Regeln befassen, selbst wenn wir sie nicht befolgen - hoffentlich nach reiflichen Überlegungen.

D
DeSharper Themenstarter:in
40 Beiträge seit 2016
vor 7 Jahren

Ich habe im ganzen Umkreis nix statisches, weder Konstruktoren noch Properties. Was genau meinst du damit?

Die Ressource ist auch da, was aber Wurscht sein dürfte, da mein BreakPoint im Converter gar nicht durchlaufen wird.

Mit dem Converter in App.xaml hat sich überhauptnichts geändert. (Nachtrag: Ich bekomme hier auch die andere ZusatzInfo (siehe unten))

Also ich hab testweise das MergedDictionary in die nächst tiefere Ebene, ins Grid, verlagert. (Was ein bisschen blöd ist, weil ich jetzt mein Fenster nicht mehr stylen kann.) Jedenfalls muss ja jetzt der Converter auf jeden Fall schon da sein.
Jetzt sehe ich zumindest zur Design-Zeit ein Fenster, statt der Fehlermeldung "Value cannot be null". Das könnte aber natürlich auch dran liegen, dass mein Window-Style jetzt auf keine kaputte Ressource mehr zugreift, sondern auf gar keine.
Leider bekomme ich noch immer zur Laufzeit eine Exception, allerdings mit einer anderen Zusatzinformation:
Additional information: "Binding" kann nicht für die Eigenschaft "Source" vom Typ "ResourceDictionary" festgelegt werden. "Binding" kann nur für eine "DependencyProperty" eines "DependencyObject" festgelegt werden.
Leider ist für mich diese ZusatzInfo noch kryptischer als die letzte. Heißt das, dass ich an der Stelle gar nichts dran binden kann? "CurrentTheme" macht brav den ganzen INotifiePropertieChanged-Kram. Oder muss ich das hier als DependencyProperty anlegen? Sorry, die Fehlermeldung hinterlässt mich verwirrter als vorher.

Noch ein paar verzweifelte Tests:

  • Wenn ich das Binding lösche und stattdessen nur Source="" schreibe, ändert sich gar nichts (Inner Exception: Value cannot be null)
  • Wenn ich das Binding lösche und stattdessen nur Source="aaa" schreibe, gibt es keine InnerException mehr
74 Beiträge seit 2014
vor 7 Jahren

"Binding" kann nicht für die Eigenschaft "Source" vom Typ "ResourceDictionary" festgelegt werden. "Binding" kann nur für eine "DependencyProperty" eines "DependencyObject" festgelegt werden.

Die Meldung ist doch eindeutig. Die Source-Property ist keine DependencyProperty, deswegen kann sie nicht gebunden werden. Die ResourceDictionary-Klasse ist noch nicht einmal ein DependencyObject.

Um ein ResourceDictionary zur Laufzeit auszutauschen musst du also ins Code Behind, einfach den Namen zu binden und durch einen Converter zu schicken geht in dem Fall nicht. Ich bin mir noch nicht einmal sicher, ob der DataContext in den Ressourcen eines Objekts überhaupt zur Verfügung steht.

Grüße

2.079 Beiträge seit 2012
vor 7 Jahren

Der DataContext steht definiti zur Verfügung, nutze ich immer wieder ^^

Was Du stattdessen machen könntest, wäre eine Ableitung von ResourceDictionary.
Ich würde dann eine art Themes-Property definieren, die eine Liste von Themes bekommt.
Jedes Theme bekommt wieder ein eigenes ResourceDictionary, z.B. mit Hilfe von Source definiert.
Ein Theme hat dann einen Namen oder irgendwas anderes, womit Du es eindeutig identifizieren kannst.

In der Ableitung kannst Du die Methode OnGettingValue überschreiben, die sieht so aus als wäre das der ideale Einstiegspunkt um das jeweils ausgewählte Theme mit einfließen zu lassen. Die originale Implementierung der Methode findest Du hier

Alternativ mergst Du die Inhalte der Themes manuell in das Dictionary. Beachte aber auch, dass Du dann die Inhalte des vorherigen Themes wieder entfernen musst.

Allgemein musst Du dir bei dem Konzept aber überlegen:
Was passiert mit einem Key, der im ResourceDictionary und im Theme doppelt vorkommt? WPF passt da nicht auf.
Ich persönlich würde sagen, der Theme-Key überwiegt, so kann ein Theme das Standard-Layout überschreiben.

Das Konzept hab ich mir von Microsofts UWP abgeschaut. Wobei ich zugeben muss, mir das noch nicht im Detail angeschaut zu haben 😄

Zuletzt musst Du dir nur noch eine gemeinsame Stelle schaffen, wo steht, welches Theme verwendet werden soll. Wenn's einfach sein soll, eine statische Property, aber das hat halt immer so seine Nachteile.
Ich würde mal ausprobieren, wie das mit einer Attached Property aussieht. Darüber kannst Du am Control den Theme setzen. Wurde nichts gesetzt, gibt sie den Theme zurück, der im Tree weiter oben zuletzt gesetzt wurde. Wurde nie etwas gesetzt, nimmt es den Wert, der an in der App.xaml gesetzt wurde oder einen Default.
So hättest Du ein Verhalten, dass sich das Theme, was Du irgendwo setzt, nach unten implizit vererbt, bis es wieder explizit gesetzt wird.

PS:
Oder Du suchst dir ein Framework, was das kann.
MahApps z.B. kann das, aber ich hab lange nichts mehr damit gemacht und weiß nicht, wie gut oder schlecht sich das nutzen lässt.

NuGet Packages im Code auslesen
lock Alternative für async/await

Beim CleanCode zählen nicht die Regeln, sondern dass wir uns mit diesen Regeln befassen, selbst wenn wir sie nicht befolgen - hoffentlich nach reiflichen Überlegungen.