WPF Global Class MVVM - c#

In trying to setup a global class in App.xaml;
<Application.Resources>
<local:MySettingsClass x:Key="GlobalSettings" />
<ResourceDictionary >
<!-- My Resources -->
</ResourceDictionary >
</Application.Resources>
Although I am getting an error message of
Each dictionary entry must have an associated key.
and
'App' does not contain a definition for 'InitializeComponent' and no extension method 'InitializeComponent' accepting a first argument of type 'App' could be found (are you missing a using directive or an assembly reference?)
Now I can go in and and an x:Key to ResourceDictionary but I have never seen anyone do that, so that seems wrong. The second error message leads me to believe that I am doing this wrong.
Do I have the wrong solution for this problem or is something simple I am missing?

Application.Resources IS a resource dictionary, implicitly. You could just slap resources right in there. This would be valid:
<Application.Resources>
<local:MySettingsClass x:Key="GlobalSettings" />
<!-- Pretty much anything with an x:Key attribute. -->
</Application.Resources>
But if you're merging dictionaries you do need the explicit <ResourceDictionary> tag, and usually in App.xaml you're merging dictionaries.
The problem you ran into is that in your XAML, the first thing it sees is your GlobalSettings resource, before anything. That's the first thing it sees, so it figures OK, he's just slapping some resources in here. It creates a ResourceDictionary and proceeds to add everything it sees to that. Next thing it sees is <ResourceDictionary>, and it thinks you're trying to add another resource dictionary to the first one as a resource, but without an x:Key attribute. I tried just now and it did let me add one with x:Key, not that I know of any reason to do that. It didn't merge the resources into the outer one.
Try this:
<Application.Resources>
<ResourceDictionary>
<!-- One of My Resources -->
<local:MySettingsClass x:Key="GlobalSettings" />
<!-- My Other Resources -->
<ResourceDictionary.MergedDictionaries>
<!-- Merged dictionaries -->
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
If you keep getting the InitializeComponent error, please share your code for App.xaml.cs

Related

Can't share ResourceDictionary between different projects

I have several Windows application projects that all have the same copy-pasted ResourceDictionary in their app.xaml file. I want to remove this code duplication, put a ResourceDictionary in one file in a project that's referred by all of them and use the ResourceDictionary.Source parameter to reference to it.
Currently every project has something like this in their app.xaml file:
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="/SomeProject;component/SomePath/First.xaml"/>
<ResourceDictionary Source="/SomeProject;component/SomePath/Second.xaml"/>
<ResourceDictionary Source="/SomeProject;component/SomePath/Third.xaml"/>
...
</ResourceDictionary.MergedDictionaries>
So I put it all in one file called Resources.xaml in a project called Common (for the example's sake), and in the app.xaml I changed the code to:
<Application.Resources>
<ResourceDictionary Source="pack://application:,,,/Common;component/Resources.xaml"/>
</Application.Resources>
When I click F12 on the filename, it directs me to the intended Resources.xaml file, but when I launch the application I get an exception:
System.Windows.Markup.XamlParseException:
''{DependencyProperty.UnsetValue}' is not a valid value for property
'Background'.'
Inner Exception: InvalidOperationException:
'{DependencyProperty.UnsetValue}' is not a valid value for property
'Background'.
I changed Resources.xaml build option to "Resource" from "Page", but it didn't change anything.
I also looked at this question, and it seems as if I'll have to change all my StaticResource references to DynamicResources, which is not a real viable solution for me.
How Can I prevent the exception? Is there any other way to prevent this code duplication?
You have to use MergedDictionaries and use the pack URI scheme to fully qualify the merged resource.
"I have several Windows application projects that all have the same copy-pasted ResourceDictionary in their app.xaml file."
Usually you create a single WPF APP project and set it as the startup project. Every additional projects are of type library. This means they don't contain an application or framework entry point, which is a class that derives from Application, usually the partial class App defined in App.xaml and App.xaml.cs. Visual Studio offers a project template for control libraries like WPF CustomControl Library or WPF User Control Library.
A WPF application contains only one active App.xaml file. If you need to reference resources in an assembly other than the startup assembly, you import them by defining a MergedDictionaries in the relevant resource files.
App.xaml
<Application.Resources>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="pack://application:,,,/SomeProject;component/SomePath/First.xaml" />
<ResourceDictionary Source="pack://application:,,,/SomeProject;component/SomePath/Second.xaml" />
<ResourceDictionary Source="pack://application:,,,/SomeProject;component/SomePath/Third.xaml" />
...
</ResourceDictionary.MergedDictionaries>
</Application.Resources>
It is recommended to move all relevant and shared resources to the App.xaml dictionary if possible. This eliminates the need to define MergedDictionaries outside of App.xaml, which can improve performance.
Also make sure the order of the merged ResourceDictionary items inside the MergedDictionaries collection are added in the right order.
Problem
Note that the XAML parser follows certain lookup rules. Also StaticResource lookup doesn't support forward declaration: all referenced resources must be defined before the declaration of the actual reference.
Especially when dealing with MergedDictionaries the order of declaration is very important.
In short the static resource lookup starts locally with the ResourceDictionary of the current element. If the resource key was not found in its scope, the XAML parser traverses up the logical tree to check the dictionaries of the logical parents, until it reaches the root element e.g. Window. After the root element the parser checks the application's resource dictionary and then the theme dictionary.
If a the parser encounters a MergedDictionaries (after checking the current ResourceDictionary first), it iterates the merged ResourceDictionary collection in reverse order from bottom to top or from last to first.
Since there is no forward declaration supported by the XAML parser, the order of the merged resources is very important.
Take the following MergedDictionaries collection:
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="/SomePath/First.xaml" />
<ResourceDictionary Source="/SomePath/Second.xaml" />
<ResourceDictionary Source="/SomePath/Third.xaml" />
</ResourceDictionary.MergedDictionaries>
Now consider the following situation: you have an element e.g. a Button that statically references a ControlTemplate, which is defined in a parent element's dictionary inside the merged dictionary of Third.xaml. But this template also contains an element, that statically references a Style defined in First.xaml.
If elements or resources declared in Third.xaml would need to statically reference resource from First.xaml, then the parser couldn't not resolve those resources: parser searches for the ControlTemplate and reaches the parent's ResourceDictionary. This dictionary doesn't contain the reference, but a MergedDictioanaries collection. So it starts to iterate over this collection in reverse order, from last to first or from bottom to top: it starts with Third.xaml and successfully finds the referenced ControlTemplate.
In order to instantiate this template, the parser must resolve all template resources. Inside this template the parser finds an element that needs a Style, but this Style was not found in any previous merged ResourceDictionary. It is defined in the ResourceDictionary of First.xaml, which has not been visited yet (forward declaration). Therefore this resource cannot be resolved.
Solution
To fix this, you can either put the merged dictionaries into the right order:
<!-- Collection is iterated in reverse order -->
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="/SomePath/Third.xaml" />
<ResourceDictionary Source="/SomePath/Second.xaml" />
<ResourceDictionary Source="/SomePath/First.xaml" />
</ResourceDictionary.MergedDictionaries>
Or replace static references with dynamic references by using the DynamicResource markup.
The DynamicResource markup instructs the XAML parser to create a temporary expression during the first lookup pass (this first lookup pass is the one described before and resolves static references at compile time). After this first pass, a second lookup occurs at runtime. The parser again traverses the tree to execute the temporary expressions previously created by the DynamicResource markup during the first lookup pass.
So whenever you can't provide a definition of a resource before its declaration, you have to use DynamicResource lookup.

How to include MaterialDesignXamlToolkit to WPF class library?

I'm trying to use MaterialDesignXamlToolkit in my WPF class library (.NET framework). I'm following their official quick start tutorial, but since i do not have App.xaml, i had to make some adjustments. Apperently some step was wrong, but i do not know which one.
1) I installed MaterialDesignXamlToolkit using Nuget.
2) I created ResourceDictionary with the following code: (i specified the key because there is an error if i don't)
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<ResourceDictionary x:Key="123">
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesignTheme.Light.xaml" />
<ResourceDictionary Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesignTheme.Defaults.xaml" />
<ResourceDictionary Source="pack://application:,,,/MaterialDesignColors;component/Themes/Recommended/Primary/MaterialDesignColor.DeepPurple.xaml" />
<ResourceDictionary Source="pack://application:,,,/MaterialDesignColors;component/Themes/Recommended/Accent/MaterialDesignColor.Lime.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</ResourceDictionary>
If i remove <ResourceDictionary x:Key="123"> element, then i get an error:
System.Windows.Markup.XamlParseException: Set property 'System.Windows.ResourceDictionary.Source' threw an exception.
FileNotFoundException: Could not load file or assembly 'MaterialDesignThemes.Wpf, Culture=neutral' or one of its dependencies.
3) My 'main screen' is Page, so i added the resource to it:
<Page.Resources>
<ResourceDictionary Source="/MyAsembly;component/ResourceDictionary/MaterialDesign.xaml" />
</Page.Resources>
4) The obvious problem occurs here (this is the second step of the official tutorial): i add the following code to my Page:
<Page ...
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
TextElement.Foreground="{DynamicResource MaterialDesignBody}"
TextElement.FontWeight="Regular"
TextElement.FontSize="13"
TextOptions.TextFormattingMode="Ideal"
TextOptions.TextRenderingMode="Auto"
Background="{DynamicResource MaterialDesignPaper}"
FontFamily="{DynamicResource MaterialDesignFont}">
But i get a warning that: The resource {MaterialDesignBody, MaterialDesignPaper, MaterialDesignFont} could not be resolved.
Some of the solutions i tried pointed out that the ResourceDictionary's build action should be page, and it is.
Any help would be greatly appreciated!
The accepted solution worked for me. To avoid the dummy code though, I was also able to get MDXT working by adding the following to the code-behind of the resource dictionary:
Assembly.LoadFrom(Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "MaterialDesignThemes.Wpf.dll"));
Assembly.LoadFrom(Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "MaterialDesignColors.dll"));
Now that i've solved the problem, i realize one important information is missing from my question: i was following MVVM pattern (so all my code behind files were empty).
The problem was with the way Revit (the application that i was building a plugin for) loads libraries that a plugin is using. I still do not understand the internal logic of it, but the following two lines added to the code behind of the first page what is being loaded solved the problem for me:
ColorZoneAssist.SetMode(new GroupBox(), ColorZoneMode.Accent);
Hue hue = new Hue("name", System.Windows.Media.Color.FromArgb(1, 2, 3, 4), System.Windows.Media.Color.FromArgb(1, 5, 6, 7));
I cannot stress enought that those two lines of code are a complite bullshit (since i do not want to place any logic to code behind), but the libraries won't otherwise be loaded. This code somehow 'forces' Revit to load Material design libraries (1st line of code uses MaterialDesignTheme.Wpf, and the 2nd MaterialDesignColors), since (i assume) it can already tell at compile time that those libraries are needed.
Remove the <ResourceDictionary x:Key="123"> element from your ResourceDictionary to begin with:
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesignTheme.Light.xaml" />
<ResourceDictionary Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesignTheme.Defaults.xaml" />
<ResourceDictionary Source="pack://application:,,,/MaterialDesignColors;component/Themes/Recommended/Primary/MaterialDesignColor.DeepPurple.xaml" />
<ResourceDictionary Source="pack://application:,,,/MaterialDesignColors;component/Themes/Recommended/Accent/MaterialDesignColor.Lime.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
You should then be able to set the properties using property element syntax after you have set the Resources property:
<Page ...
d:DesignHeight="450" d:DesignWidth="800">
<Page.Resources>
<ResourceDictionary Source="/MyAsembly;component/ResourceDictionary/MaterialDesign.xaml" />
</Page.Resources>
<Page.Background>
<DynamicResource ResourceKey="MaterialDesignPaper" />
</Page.Background>
</Page>
Without adding those lines.
Double check if the MaterialDesign dll file get copied to the output path of the application.
I have seen such issue before, just adding nonsense code and Visual Studio realize your application that depends on your lib also depends on MaterialDesign lib and then copies the dll again as one would expect in the first place.
Instead of adding those lines you could then
Reference MaterialDesign directly in your application as well
Use a build event to make sure the DLL is copied to the build path.
This comment solves the problem for me,
but make sure you don't have another errors and if you have just find them and fix theme then try to run the project and it will work.
using MaterialDesignColors;
using MaterialDesignThemes.Wpf;
public MainWindow()
{
InitializeMaterialDesign();
InitializeComponent();
}
private void InitializeMaterialDesign()
{
// Create dummy objects to force the MaterialDesign assemblies to be loaded
// from this assembly, which causes the MaterialDesign assemblies to be searched
// relative to this assembly's path. Otherwise, the MaterialDesign assemblies
// are searched relative to Eclipse's path, so they're not found.
var card = new Card();
var hue = new Hue("Dummy", Colors.Black, Colors.White);
}

Styles from resource dictionary not found in user control

I have a resource dictionary with a bunch of styles that I am linking too in my user controls like so:
<UserControl.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="../Theme/ThemedResources.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</UserControl.Resources>
This all works fine during runtime.
However I get a recurring problem in that one of the styles in the resource dictionary 'cannot be found' in whichever user control is the most recent, until the solution is rebuilt. This error will then appear again after I stop the debugging process, and again will disappear with a rebuild.
I don't think this is linked to a specific style, as when I add new styles the style that can't be found seems to change.
Any ideas how I can stop this from happening?
You have used a Relative URI, so it will looks for your resource file in a somewhere that you used your UserControl. It can't find you resource file because your resource file is not in the AbsolutePath.
AbsolutePath = CurrentPath (r.g Where you used your UserControl) + RelativePath
so Use an AbsolutePath:
<ResourceDictionary Source="pack://application:,,,/{YourAssemblyName};component/Theme/ThemedResources.xaml" />

Merged ResourceDictionary initalization in UWP app

During development of my UWP app I have noticed and intersting oddity which I have hard time explaining.
I user MvvmLight and I decided to add the ViewModelLocator resource instance in a separate ResourceDictionary Core.xaml which will be referenced from MergedDictionaries in App.xaml.
Following is the content of App.xaml:
<Application ...>
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Resources/Core.xaml" />
<ResourceDictionary Source="Resources/Converters.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
</Application>
Contents of Core.xaml:
<ResourceDictionary ...>
<viewModel:ViewModelLocator x:Key="Locator" />
</ResourceDictionary>
Now I supposed the resources in Core.xaml are initialized during the InitializeComponent method call in App.xaml.cs, but when I tried to use the ServiceLocator class (which is set in the constructor of ViewModelLocator in MvvmLight) - like this - ServiceLocator.Current.GetInstance<INavigationService>().Navigate<MainViewModel>(); - I get an exception saying:
An exception of type 'System.InvalidOperationException' occurred in
Microsoft.Practices.ServiceLocation.dll but was not handled in user code
Additional information: ServiceLocationProvider must be set.
Indeed, if I put an breakpoint in the constructor of ViewModelLocator, it is not called before the Window is activated. More interestingly still - if I manually reference the Locator resource key (for example putting Debug.WriteLine(Resources["Locator"]); above the call of ServiceLocator), everything works fine. The same goes if I move the ViewModelLocator resource directly to App.xaml - then it is instantiated during IntializeComponent.
Is there a lazy instantiation of merged resource dictionaries in UWP apps? Or why does it behave this way?
A ResourceDictionary in UWP doesn't have any code behind (no InitializeComponent). Therefore, any class references defined in a ResourceDictionary won't be initialized directly.
Neither does the App.InitializeComponent do this for you. Resource dictionaries in UWP just don't provide this functionallity - don't ask me why.
You can easily try this by trying to initialize a DataTemplate in a ResourceDictionary.
This should - sadly - neither work.
However, using the Resources["Locator"] access in code behind triggers the constructor of the class and you're fine.
This ain't be a solution, but a explanation of your problem.
I hope it helps you.

What kind of "Key" do I need to supply when loading Resource Dictionaries?

In trying to set a default ResourceDictionary I receive the following warning:
The designer does not support loading dictionaries that mix
'ResourceDictionary' items without a key and other items in the same
collection. Please ensure that the 'Resources' property does not
contain 'ResourceDictionary' items without a key, or that the
'ResourceDictionary' item is the only element in the collection.
This is the code that I am using in my App.xaml file, that received the above warning:
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Lang.en-US.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
This is the exact same code that I've used to set a ResourceDictionary in Visual Studio 2008. I am now using VS 2010. What Key do I need to provide this ResourceDictionary for it to work correctly?
This is the line in my MainWindow.xaml that I am currently testing along with this code:
<MenuItem Header="{DynamicResource new_test}" />
Since you haven't posted your complete XAML file, i suspect there are other resources apart from merged dictionary in your resources section.
As per MSDN -
It is legal to define resources within a ResourceDictionary that is
specified as a merged dictionary, either as an alternative to
specifying Source, or in addition to whatever resources are included
from the specified source. However, this is not a common scenario; the
main scenario for merged dictionaries is to merge resources from
external file locations. If you want to specify resources within the
markup for a page, you should typically define these in the main
ResourceDictionary and not in the merged dictionaries.
Try moving other resources in separate resource dictionary and make sure all other resources have x:Key set on them -
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Lang.en-US.xaml" />
</ResourceDictionary.MergedDictionaries>
<ResourceDictionary>
<BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter"/>
<ContextMenu x:Key="MyContextMenu"/>
</ResourceDictionary>
</ResourceDictionary>
</Application.Resources>
Use resource file for translations. Its better than resource dictionary.
Here is an example:
Set prefix like this for usage in xaml.
xmlns:const="clr-namespace:FileExplorer.Properties"
Resources are located in properties.
To use them in XAML you will need following:
<TextBox Text="{x:Static const:Resources.Window_Title_String}"/>
If you have different languages then create for each language own resource file following naming convention.
For example:
Resources.resx (this will be default)
Resources.de-DE.resx (this is for german)
Now you just have to set current culture to german for your app to be on german and the proper resource file will be used automatically.
Like this in Main method:
Thread.CurrentThread.CurrentUICulture = new CultureInfo("de-DE");

Categories