Headaches with configuring themed resources from c# (Windows Phone) - silverlight

I have Windows Phone App and I want to have a different themed resources based on whether the user has a dark or light theme and which accent color they have chosen.
In my app initialize code I detect the users theme and accent color then load the appropriate Resource Dictionary, Dark, Light etc. I add the Resource Dictionary to the App.Current.Resources.MergedDictionaries collection.
The problem comes when I want to reference a value in the (dynamically loaded) from my App.Xaml. In the below example the key is "DefaultBackgroundImageOpacity". For some reason the values in the ResourceDictionary that I load into the MergedDictionaries collection never get found when the App.Xaml parsing happens. I've played with loading the resources before I call InitializeComponent() and after. Neither seem to resolve the issue.
Any ideas?
// Simplified version of adding some xaml...
// Note I'm loading the key "DefaultBackgroundImageOpacity"
this.Resources.MergedDictionaries.Clear();
var myTestXaml = "<ResourceDictionary xmlns='http://schemas.microsoft.com/winfx/2006/xaml/presentation' xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml' xmlns:System='clr-namespace:System;assembly=mscorlib'> <System:Double x:Key='DefaultBackgroundImageOpacity'>0.2</System:Double></ResourceDictionary>";
this.Resources.MergedDictionaries.Add((ResourceDictionary)XamlReader.Load(myTestXaml));
// This always fails saying that the key "DefaultBackgroundImageOpacity"
// can not be foudnd - even though it was loaded just above
InitializeComponent();
// Phone-specific initialization
InitializePhoneApplication();

I suspect your App.Xaml contains something like this:-
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="/somedictionary.xaml" />
...
</ResourceDictionary.MergedDictionaries>
...
</ResourceDictionary>
</Application.Resources>
If that is so then InitializeComponent will replace the default ResourceDictionary which you have manipulated in your code hence your changes to MergeDictionaries will not be present since that instance of a ResourceDictionary is no longer referenced.
If you are not setting up any MergedDictionaries in the App.Xaml then make sure you add resources directly to Applicaiton.Resources and are not creating an new instance of ResourceDictionary in the xaml.

Related

Merging ResourceDictionaries changing custom control pathing

Net 5 WFP app issue VS v.16.9
My custom control, which is supposed to display a plain isn't displaying at run. I think I got an idea what's going on but don't know how to fix it.
Merging resource dictionaries in my Generic.xaml file to references the source of my custom control causes the blue squiggly line and mouseover tooltip
"Cannot locate resource 'ControlsFolder/CustomControl.xaml'"
If I delete my Merged dictionary, the mouseover tooltip changes to say "Projectnamespace.ControlsFolder.CustomControl"
The tooltipped CustomControl being called in my MainWindow.xaml as follows.
<Control:CustomControl/>
The Merged of dictionaries that I think is causing the problem is ...
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="/ControlsFolder/CustomContol.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
My CustomControls.cs has the constructor below
static CustomControl()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(CustomControl), new FrameworkPropertyMetadata(typeof(CustomControl)));
}
Its my understanding that this constuctor searches the Themes/Generic.xaml file to figure out where the location of my customcontrol is to add default metadata to my customcontrol. But I think when I add my merged resource dictionary, its changing the pathing of my CustomControl call to search for my styles in /ControlFolder/CustomControl.xaml instead of default pathing which should be "Projectnamespace.ControlsFolder.CustomControl".
Or I made a simple mistake that's not intuitive at all for a beginner.
Maybe there is a property I can add to CustomControl that sets the pathing back to the other one. Does anyone have any ideas what the issue is?
But I should be seeing my style display on screen
Welcome to SO! Try this URI instead:
<ResourceDictionary Source="/YourProjectName;component/ControlsFolder/CustomContol.xaml" />

Edit ResourceDictionary from an external assembly

I've been searching around if it is possible for an assembly to change the ResourceDictionary values from another assembly at runtime. So far I've found nothing.
Here´s the deal. I have a UserControl that will work independently so it can be fit into different projects. My UserControl has its own Resources.xaml (Compiled as Resources).
I have a second assembly that it used as a setup tool for this user control. It basically just reads the UserControl Resources.xaml (which is working great) and then replaces the values of the Resources.Xaml. Trouble is, I cannot change the resource values.
Here's the code I use on my setup tool to read the ResourceDictionary:
<Window.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="pack://application:,,,/Control;component/Configuration/Resources.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Window.Resources>
To change this values I've tried something like:
this.Resources.MergedDictionaries.First()["IsZoomable"] = false;
It does recognize the resources but it wont change it. Is it even possible to change the values of the dictionary of another assembly at runtime or will I need to create an external dictionary that can be acessed by both assemblies?
EDIT:
The user control contains the Resources.xaml. I've defined the Resources in this control like this:
<UserControl.Resources>
<ResourceDictionary Source="Configuration/Resources.xaml"/>
</UserControl.Resources>
The setup tool can acess this resources but I'm not able to change them. When I do, the user control still reads the old values. Resources are defined in the setup tool like so:
<Window.Resources>
<ResourceDictionary Source="pack://application:,,,/Control;component/Configuration/Resources.xaml"/>
</Window.Resources>
Yes, you can change the value of a resource at runtime.
Since you have merged the dictionary, you can change the value like this:
this.Resources["IsZoomable"]=false;
Make sure that the resource key matches the actual key.
If you have merged the dictionary in app.xaml, then you can use:
Application.current.Resources["IsZoomable"]=false;

Override Control Style

I have created a CustomControlLibrary.dll, that contains a Control (MyControl), with its style in the Generic.xaml of the Themes folder, as per the default automatic setup.
If I want to include that dll in 2 different projects, each of which providing a custom style for "MyControl" to give it a different look, where do I put those custom styles?
I thought I just had to have a Themes\Generic.xaml for each application, that defines the custom style, but having done this, it still ends up using the style defined in CustomControlLibrary.dll
You have CustomControlLibrary.dll which is the owner of MyControl. Inside that dll you have your themes defined. Any other project that may contain your dll doesnt haven to have themes defined.
Other projects may define their custom style for MyControl in their window resource for example.
Styles may be defined at any level. :)
Check this link out:
http://msdn.microsoft.com/en-us/library/ms745683.aspx
Solution will be to define specific style for each project and apply it correspondingly.
But keep in mind that you can create proper style "inheritance" by using BasedOn property. After all you should get a set of styles that you'll manage in MergedDictionaries:
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="pack://application:,,,/CustomControlLibrary;component/styles.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>

WPF Dynamically change resource file and theme

My project uses a ProjectTheme.xaml file for all WPF windows through out the project.
The ProjectTheme.xaml file references a style theme as follows
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<ResourceDictionary.MergedDictionaries>
<!-- In order to modify the project's theme, change this line -->
<ResourceDictionary Source="/MyProject;component/Themes/WPFThemes/Customized.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
All WPF Windows references WindowBase.xaml
<Window.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="/MyProject;component/View/WindowBase.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Window.Resources>
WindowBase.xaml references customized titlebar Bar1.xaml
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="/MyProject;component/Themes/WPFThemes/Bar1.xaml" />
</ResourceDictionary.MergedDictionaries>
Bar1.xaml references ProjectTheme.xaml
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="/MyProject;component/ProjectTheme.xaml"/>
</ResourceDictionary.MergedDictionaries>
So the heriarchy is
Window1 references WindowBase.xaml
WindowBase references Bar1.xaml
Bar1 references ProjectTheme.xaml
ProjectTheme.xaml reference the real theme resource file.
This works fine.
Now I want to dynamically change the project theme at run time without quitting the app.
Assuming that I have several theme style files
Customized.xaml
Customized1.xaml
Customized2.xaml
My question is
if it possible to dynamically update ProjectTheme.xaml file at run time to change the line
from
<ResourceDictionary Source="/MyProject;component/Themes/WPFThemes/Customized.xaml" />
to
<ResourceDictionary Source="/MyProject;component/Themes/WPFThemes/Customized1.xaml" />
to achieve my objective?
If yes, how do I do it?
If no, what is the reason and what is the best (other) way to achieve my purpose?
I have tried the following but none of them work: the style does not change.
way 1
Application.Current.Resources.MergedDictionaries.Clear();
Uri NewTheme = new Uri(#"/MyProject;component/Themes/WPFThemes/Customized2.xaml", UriKind.Relative);
ResourceDictionary dictionary = (ResourceDictionary)Application.LoadComponent(NewTheme);
Application.Current.Resources.MergedDictionaries.Add(dictionary);
way 2
Application.Current.Resources.MergedDictionaries.RemoveAt(0);
Uri NewTheme = new Uri(#"/MyProject;component/Themes/WPFThemes/Customized2.xaml", UriKind.Relative);
ResourceDictionary dictionary = (ResourceDictionary)Application.LoadComponent(NewTheme);
Application.Current.Resources.MergedDictionaries.Insert(0, dictionary);
Note:
In my real theme style files (Customized.xaml...) I used a combination of dynamic resource and static resource. Does that matters?
Thanks in advance.
There are a few things to consider here.
First, anything defined with StaticResource will not get updated on a change. If you want a control to support changing the theme at runtime, you need to use DynamicResource so it knows to look for changes.
Your overall approach to changing the theme is correct. The easiest way to accomplish this is using Application-scoped resource dictionaries, making sure your ResourceDictionary is defined in your App.xaml. For adding a new resource, I've used snippets similar to the following:
ResourceDictionary dict = new ResourceDictionary();
dict.Source = new Uri("MyResourceDictionary.xaml", UriKind.Relative);
Application.Current.Resources.MergedDictionaries.Add(dict);
The part you may be confusing yourself over is when using resources within base classes. When you define a resource in a class, the resource will be local to an instance of that type. Think of the XAML compiling into it's own InitializeComponent() method on classes, meaning you can't change the original XAML and expect the changes to go to all instances. On the same note, changing the resources on a class instance doesn't effect other instances.
Since your question really contains two separate concerns (application theming and changing control resources), I would focus on ensuring your application resources are updating properly and using DynamicResource, and hopefully the information I've provided would be sufficient for understanding why certain other resources may not be updating yet.

Static resource shared in merged dictionaries

I'm currently working on having dictionaries of styles and templates that I can dynamically apply to my application. Before this "new wanted" dynamical behavior, I had several resource dictionaries, one for each styled control, that I merged in the App.xaml:
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="ColorsDictionary.xaml"/>
<ResourceDictionary Source="ControlsTemplatesDictionary.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
Now, I'd like my application to be styled, so I decided to merge all my previous resources into a new one called "MyFirstTemplates" and to add only this dictionary to the App.xaml.
New dictionary "MyFirstTemplates.xaml":
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">"
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="ColorsDictionary.xaml"/>
<ResourceDictionary Source="ControlsTemplatesDictionary.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
New App.xaml:
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="MyFirstTemplates.xaml"/>
</ResourceDictionary.MergedDictionaries>
<Style TargetType="{x:Type Window}"/>
</ResourceDictionary>
</Application.Resources>
Note: The default style for the Window is to correct a bug of WPF 4, see Adding a Merged Dictionary to a Merged Dictionary
Now that I have made this change, I cannot use a color resource from "ColorsDictionary.xaml" as a StaticResource in "ControlsTemplateDictionary.xaml" anymore. If I change back to merging these files in the app.xaml, everything works. To make it work, I have to change these StaticResource for DynamicResource. Do you have any idea why this doesn't work anymore?
Thank you :-)
By moving the dictionaries out of App.xaml the resources from each dictionary aren't in the other's resource tree during loading of MyFirstTemplates.xaml. Your original setup first loaded ColorsDictionary which was then available through App resources to ControlsTemplatesDictionary while it loaded. In your new setup, in order for the color resource to be available in App resources it needs to be loaded through MyFirstTemplates, which in turn requires loading of both dictionaries, which in turn requires access to the color resource... so it's sort of an infinite loop of references that can't be resolved statically. DynamicResource can wait until everything is loaded and then access the color without issue.
To fix either use Dynamic or merge ColorsDictionary directly into ControlsTemplatesDictionary.
Great answer by John explaining why this is happening.
So the problem is that when using merged dictionaries within a merged dictionary, the inner dictionaries can't "use" each other as StaticResource.
Basic solutions:
Use DynamicResource
Use just a single level of hierarchy from App.xaml when using StaticResource
Both of these solutions have problems. DynamicResource has a performance problem. The 2nd solution limits you on how you organize your XAML resources.
Alternative solution:
I created a small simple program (provided below in GitHub) that will run as a pre-build event and merge XAML files from a folder into one long .XAML file. Well, they need to be with a different extension (.txaml), otherwise they will be compiled.
This allows to structure resources folders and files however you want, without WPF’s limitations. StaticResource and the designer will work always.
The code in GitHub contains a simple solution that contains the merging program. It merges 2 folders into 2 files. One for App.xaml resources and the other for Generic.xaml resources. The .xaml files in a "Controls" project (There's also "Main" project).
Blog post explaining this

Resources