Laden...

Änderung an dynamic resource in ResourceDictionary von UserControls nicht bemerkt?

Erstellt von MillionsterNutzer vor 9 Jahren Letzter Beitrag vor 9 Jahren 2.641 Views
M
MillionsterNutzer Themenstarter:in
235 Beiträge seit 2005
vor 9 Jahren
Änderung an dynamic resource in ResourceDictionary von UserControls nicht bemerkt?

Hallo!

Ich würde meinem Benutzer gern die Möglichkeit geben einige Farben in der GUI zu ändern. Ich dachte dabei dass es mit Dynamic Resouces am einfachsten geht, habe aber momentan noch Probleme. Hier mein Aufbau:

Ich habe ein ResourceDictionary für Farben welches in der Datei Colors.xaml enthalten ist. In diesem ResourceDictionary habe ich den SolidColorBrush 'CaptionLabelBrush' definiert.
In einem weiteren ResourceDictionary welches in der Datei Controls.xaml enthalten ist habe ich ein Style für Labels namens 'CaptionLabel' definiert welches als Background den zuvor genannten SolidColorBrush als DynamicResource verwendet. Damit das geht wurde im zweiten ResourceDictionary das erste als MergedDictionary angegeben.

In meiner inzwischen recht komplexen Oberfläche wird nun an vielen unterschiedlichen Stellen das Controls-ResourceDictionary als MergedDictionary verwendet. Dies wäre zwar zur Laufzeit eigentlich nicht nötig da es ja vom Window nach unten and UserControls durchgereicht wird, allerdings sieht sonst im Cider die Vorschau gänzlich falsch aus.
Die verwendeten UserControls aber auch das Controls-ResourceDictionary stammen aus separaten Projekten bzw. dlls.
Das einbinden der MergedDictionaries erfolgt mit Hilfe einer eigenen SharedResourceDictionary Klasse welche sicherstellt das ein und das selbe ResourceDictionary nicht mehrmals von unterschiedlichen UserControls neu geladen und instanziert wird. Es sollte folglich in der ganzen Anwendung nur eine Instanz des Controls-ResourceDictionary geben welches von allen Windows/UserControls genutzt wird.

Nun war mein Ziel das der Benutzer die Farbe die im CaptionLabelBrush definiert ist zur Laufzeit ändern kann damit im Anschluß alle CaptionLabels die neue Farbe erhalten. Das seltsame ist dass das auch funktioniert, aber nicht überall gleich: Je nachdem wie und wo ich in meinem Code das ResourceDictionary zu erreichen versuche wird die Änderung sichtbar oder scheinbar ignoriert. Folgende Möglichkeiten konnte ich ausfindig machen:


SharedResourceDictionary controlsResDict = new SharedResourceDictionary();
controlsResDict.Source = new Uri("pack://application:,,,/Common.Themes;component/Controls.xaml");
if (controlsResDict Resources.Contains("CaptionLabelBrush"))
{
   controlsResDict ["CaptionLabelBrush"] = theNewBrush;
}

if (this.Resources.Contains("CaptionLabelBrush"))
{
    this.Resources["CaptionLabelBrush"] = theNewBrush;
}

if (Application.Current.Resources.Contains("CaptionLabelBrush"))
{
    Application.Current.Resources["CaptionLabelBrush"] = theNewBrush;
}

Leider hängt es scheinbar davon ab wo ich die Änderung durchführe um damit erfolgreich zu sein (App.xaml.cs oder ViewModel oder auch direkt inm CodeBehind). Auch hatte ich teilweise das Phänomen das manche UserControls die neue Frabe erhielten und andere nicht, obgleich diese jeweils auf der gleichen Ebene standen jedoch aus unterschiedlichen dlls kamen.
Letztlich scheint mir nur der Weg über 'this.Resources' zuverlässig zu sein allerdings ist dieser auch am Aufwendigsten da dies direkt im CodeBehind zu geschehen hat und dies ein ernsthaftes Verteilungsproblem darstellt.

Hat jemand von euch eine Idee oder einen Hinweis was ich falsch mache bzw. wie man das richtig angeht?
Hat jemand von euch so ein Szenario bereits erfolgreich gelöst?

Viele Grüße

Ralf

P
157 Beiträge seit 2014
vor 9 Jahren

Eigentlich reagiert das System nur auf Änderungen von Eigenschaften einer Resource und nicht auf die Änderung der Resource selbst. Versuch mal einfach dein vorhandenes Brush anzupassen anstatt es zu überschreiben. Ich vermute mal dass kein CollectoinChanged vom Resourcedictionary abonniert wird.

vg

Wenn's zum weinen nicht reicht, lach drüber!

M
MillionsterNutzer Themenstarter:in
235 Beiträge seit 2005
vor 9 Jahren

Hallo Parso,

vorweg muss ich wohl noch einge Dinge zu meinem Problem erwähnen die ich Heute herausbekommen:

  1. Meine SharedResourceDictionary Klasse hat bisher als Key value die Uri zur Resource verwendet. Dabei hat sich herausgestellt das eine andere Schreibweise dazu führt das ein neuer Eintrag im Dictionary gemacht wird. Die beiden Uris

"pack://application:,,,/Common.Themes;component/Controls.xaml"
und
"pack://application:,,,/Common.Themes;component/CONTROLS.xaml"

führen also dazu das auf unterschiedliche ResourceDictionaries verwiesen würde.
Tatsächlich hatte ich dieses Problem in meinem Code auch. Nachdem ich meine SharedResourceDictionary-Klasse also etwas toleranter gemacht habe bekomme ich nun etwas konstantere Ergebnisse.

  1. Der Weg über "Application.Current.Resources" scheint mir nur innerhalb eines begrenzten Bereichs zu funktionieren (Ich vermute mal das es nur innerhalb eines Windows oder einer Assembly zu greifen scheint, was für mich leider nicht ausreichen ist).

  2. Wie bereits ursprünglich erwähnt ist der Weg über "this.Resources" für mich nicht tragbar.

Ich habe mich also dafür entschieden meinen CaptionLabelBrush-Wert zu manipulieren indem ich das ResourceDictionary über meine SharedResourceDictionary-Klasse abgreife. Dabei ist mir nun jedoch aufgefallen dass ich hier den Wert nur ändern kann wenn das ResourceDictionary noch nicht 'verwendet' wurde. Das heißt ich kann den Wert also nur einmal beim Initialisieren meiner Anwenung setzen. Danach hat die Änderung keine Auswirkung mehr.
Wenn ich nun versuche den Farbwert des SolidColorBrush zu überschreiben anstatt den ganzen Brush zu tauschen dann klappt das auch nur beim ersten mal - danach is das IsFrozen-Property des Brush auf true und ich kann nichts mehr daran ändern.

Das ist alles was ich soweit in Erfahrung bringen konnte. Im schlimmsten Fall werd ich es dabei belassen müssen - ich kann nicht ganze Tage investieren um ein paar Farben zur Laufzeit zu ändern. Aber falls jeman hier noch eine Idee wie man das Problem noch in den Griff bekommen könnte...

Viele Grüße

Ralf

5.657 Beiträge seit 2006
vor 9 Jahren

Hi MillionsterNutzer,

da das ResourceDictionary so viele Probleme macht, wäre es evtl. angebracht darüber nachzudenken, ob das eigentlich wirklich notwendig ist, oder ob es nicht einen anderen, vielversprechenderen Weg gibt.

Ich weiß zwar nicht, warum du dich für diesen Weg entschieden hast, aber für mich ist die Anforderung, daß ein Benutzer bestimmte Farben ändern kann, ein Teil der Businesslogik, nicht der Benutzeroberfläche.

Anstatt die Farben an Resourcen zu binden, könntest du sie einfach an ein Property einer Konfigurations-Klasse binden. Die Konfiguration enthält dann alle einzelnen Farbeinstellungen für den aktuellen Benutzer. Um die Konfiguration zu speichern und wieder zu laden, kannst du auf XML-Serialisation zurückgreifen oder auf das [Tutorial] Konfigurationsmodell im .NET Framework.

So hättest du die Logik zum Definieren der Farben im (View-)Model gekapselt, und die GUI muß darüber nichts mehr wissen.

Christian

Weeks of programming can save you hours of planning

J
641 Beiträge seit 2007
vor 9 Jahren

hilft vielleicht das : x:Shared-Attribut
auf false setzen!

cSharp Projekte : https://github.com/jogibear9988

M
MillionsterNutzer Themenstarter:in
235 Beiträge seit 2005
vor 9 Jahren

Hallo jogibear9988, Hi MrSparkle,

Das x:Shared-Attribut ist in meinem Fall, wenn ich die Doku recht verstanden habe, nicht geeignet. Ich habe auch kurz versucht dieses einzusetzen allerdings ohne Erfolg.

MrSparkles Vorschlag leuchtet mir nicht ganz ein: Zwar könnte ich wohl die Farbe über das Konfigurationsmodell speichern, aber wie binde ich diese dann an die Hintergrundfarbe aller meiner 'CaptionLabels' ohne dabei für jedes Element explizit ein Binding angeben zu müssen?

Viele Grüße

Ralf

P
660 Beiträge seit 2008
vor 9 Jahren

aber wie binde ich diese dann an die Hintergrundfarbe aller meiner 'CaptionLabels' ohne dabei für jedes Element explizit ein Binding angeben zu müssen?

in dem du einen Style definierst. Am besten einen der ein {x:Type CaptionLabels} verwendet.

MfG
ProGamer*Der Sinn Des Lebens Ist Es, Den Sinn Des Lebens Zu Finden! *"Wenn Unrecht zu Recht wird dann wird Widerstand zur Pflicht." *"Ignorance simplifies ANY problem." *"Stoppt die Piraterie der Musikindustrie"

M
MillionsterNutzer Themenstarter:in
235 Beiträge seit 2005
vor 9 Jahren

hi ProGamer,

Hmm, ich komm nicht drauf wie das funktionieren soll:

Ich habe ja bereits ein Syle namens CaptionLabel. Diesen weise ich eben jedem Label zu welches eine Überschrift sein soll. Da ich das entsprechende RessourceDictionary schon im XAML meiner Windows uns UserControls definiert habe wird auch im Cider bereits die richtige Formattierung angezeigt.
Der von dir genannt Style wird dann wohl eher zur Laufzeit erstellt und im CodeBehind zugewiesen, richtig?

VG

Ralf

P
660 Beiträge seit 2008
vor 9 Jahren

Hallo,

Der von dir genannt Style wird dann wohl eher zur Laufzeit erstellt und im CodeBehind zugewiesen, richtig?

uhm, Nein.
was ich meine ist folgendes:


	<Style TargetType="{x:Type CaptionLabels}">
		<Setter Property="Template">
			<Setter.Value>
				<ControlTemplate TargetType="{x:Type CaptionLabels}">
					[...]

innerhalb von ControlTemplate kannst du dann mit TemplateBinding arbeiten.

oder ich verstehe dein vorhaben total falsch O.o

MfG
ProGamer*Der Sinn Des Lebens Ist Es, Den Sinn Des Lebens Zu Finden! *"Wenn Unrecht zu Recht wird dann wird Widerstand zur Pflicht." *"Ignorance simplifies ANY problem." *"Stoppt die Piraterie der Musikindustrie"