Application-level Resources in a Different Assembly - wpf

This question involves the Visual Studio (2008) WPF Designer's apparent inability to handle the usage of resources located at the App.xaml level if the App.xaml is in a separate assembly from the view.
To simplify the explanation of the problem I have created a test application. This application has two assemblies: View and Start. The View assembly contains a xaml window called Window1, and the Start assembly includes the App.xaml file. The App.xaml file in the Start assembly has its StartupUri set to the Window1 in the View assembly. Neither of these files have code-behinds (aside from the standard constructors and InitializeComponent() call).
The code for this example is as follows:
App.xaml:
<Application x:Class="Start.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
StartupUri="pack://application:,,,/View;component/Window1.xaml"
>
<Application.Resources>
<!-- Warning Text Style -->
<Style x:Key="WarningTextStyle" TargetType="TextBlock">
<Setter Property="FontWeight" Value="Bold" />
</Style>
</Application.Resources>
Window1.xaml:
<Window x:Class="View.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1"
Height="300"
Width="300"
>
<Grid>
<TextBlock Text="This is test text" Style="{StaticResource WarningTextStyle}" />
</Grid>
</Window>
The Window1.xaml file contains a single TextBlock that references the App-level WarningTextStyle. This code works fine at runtime because the Window properly finds the App-level resource; however, at design-time the designer complains that it cannot find the WarningTextStyle.
Does anybody know of a clean and scalable solution to this problem?
My standard approach with large applications is to organize my app-level resources into resource dictionary files, and then merge those dictionaries in the App.xaml. To work around the problem that I've described above I have to merge those resource dictionaries into each view's resources. This seems very inefficient, and if I later add another resource dictionary then I need to merge that new dictionary into every view.
A silver bullet solution would re-direct the designer to find the app-level resources. A reasonable work around would be the merging of the app-level resource dictionaries into each view, but only at design-time. At runtime I would like to avoid merging these dictionaries in every view because of the efficiency issues.
I've tried merging the dictionaries on each view in the view's code-behind constructor, and then wrapping that logic in an if statement that checks the DesignerProperties.GetIsInDesignMode() method; however, the Visual Studio designer does not run the view's constructor - so this approach appears to be a bust.
Does anybody have a better solution or work around?

Can you merge the resource dictionary in your referenced assembly (be it App.xaml or your own resource dictionary) from your main (exe) assembly's App.xaml?

I just had a different idea: use a DynamicResource instead of a Static one. This might introduce a tiny performance hit, but I doubt it would be measurable.

Related

IOException when referencing App.xaml's ResourceDictionary

I'm trying to reference App.xaml's ResourceDictionary from a separate WPF window. I want to use resources from there in different windows, and this seems like the recommended way to do it. Unfortunately, I don't seem to be able to effectively reference App.xaml from the other window. Here is my App.xaml:
<Application x:Class="View.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:ViewModel="clr-namespace:ViewModel;assembly=ViewModel"
xmlns:local="clr-namespace:View"
StartupUri="ClockView.xaml">
<Application.Resources>
<ResourceDictionary>
<local:PriorityToIconConverter x:Key="PriorityToIconConverter" />
</ResourceDictionary>
</Application.Resources>
Notes: I'm not using MainWindow, so I've replaced the startup URI with a form that always comes up. I noted that in some other answers, the location of MainWindow is sometimes the issue. In my case, I haven't seen any difference between using ClockView or MainWindow. Both ClockView and MainWindow exist in the root namespace, MainWindow is just never loaded. I also have more resources, but I've removed them for the sake of conciseness.
Here's a simplified example of the code where I'm trying to reference the ResrouceDictionary from App.xaml:
<local:AssistantWindow
x:Class="View.AutomatorView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:properties="clr-namespace:View.Properties"
xmlns:local="clr-namespace:View"
mc:Ignorable="d"
Title="Tool"
x:Name="Tool"
Background="Transparent"
Height="600"
Width="450"
Topmost="{Binding Source={x:Static properties:Settings.Default}, Path=ToolAlwaysOnTop}"
MinHeight="515"
MinWidth="150">
<Window.Resources>
<ResourceDictionary Source="App.xaml" />
</Window.Resources>
Again, this is simplified to be concise. When I try to load this form, I get the exception:
System.Windows.Markup.XamlParseException: ''Set property 'System.Windows.ResourceDictionary.Source' threw an exception.' Line number '21' and line position '10'.'
Inner Exception
IOException: Cannot locate resource 'tool/app.xaml'.
The view for "Tool" is located in a folder that is also named "Tool." However, the xaml and code behind don't reference this namespace, I'm just using the folder to organize my classes. It looks like it's looking for App.xaml in the folder the view resides in. App.xaml resides in the root namespace (View). I've tried modifying the source in the xaml for Tool to:
- View.App.xaml
- View:App.xaml
- View/App.xaml
How can I get this reference to work, so I can share resources throughout my application? Thank you.
You can't load App.xaml like you're trying to do because it's not actually a ResourceDictionary. You can only specify ResourceDictionary files as the target of Source.
However, if you declare a resource in App.xaml, you can reference it anywhere without needing to load the file it's in. That's done for you automatically. Therefore, you can reference your converter at any time with {StaticResource PriorityToIconConverter}.
Note that if you moved it from the default starting location (the base project folder) you may have to update its location. Right click your project, then Properties. Navigate to the "Application" tab (should be the uppermost element on the left-hand sidebar) and look for the "Startup object" field. Set that to [ProjectName].[Namespace?].[Namespace?].App. When I tested it, mine worked without needing to manually change the location, but your setup may be different.

How to correctly reference external xaml styles in a WPF application?

I believe my question is fairly simple and yet I am having difficulty implementing it successfully. I simply wish to extract the styling of elements in my WPF application because the xaml is rather crowded and xaml is often duplicated.
I therefore wish to place the styling in an external xaml file, in the form of a resource dictionary, then reference that file in the resources section of my code.
I have the following .xaml file:
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Style x:Key="PTextBox" TargetType="TextBox" x:Name="PTextBox">
<Setter Property="Foreground" Value="#FFA1C8E7"/>
<Setter Property="BorderBrush" Value="#FFA1C8E7"/>
</Style>
And I reference the dictionary here:
<UserControl.Resources>
<ResourceDictionary x:Key="PegasusStyles">
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="../../Resources/Styles/PegasusStyles.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</UserControl.Resources>
Visual studio has resolved the file location so I know this reference is correct.
The text box the styles are applied to then references the style:
<TextBox Style="{StaticResource PTextBox}"/>
If left as a static resource I get a xaml parse error like so:
An unhandled exception of type 'System.Windows.Markup.XamlParseException' occurred in PresentationFramework.dll
And if I make the resource dynamic then the styles simply do not get applied at runtime.
I'm not sure if xaml files require certain properties before run time but mine are as follows:
If someone could answer this mystery it would be wonderful. I googled till my fingers bled but none of the answers posted by others have resolved my issues and this seems very rudimentary.
EDIT: Solved. Switching the build action to Page instead of resource has fixed my issue as suggested by Andrew Stephens. This had been hidden by another underlying problem, which is that I had added a boolean to visibility converter (common tool) to my resources. This alone is fine but once I had declared a resource dictionary this converter needed to be brought inside the dictionary as well.
It sounds like a XAML syntax error somewhere, but can also be caused by an unhandled exception in the main window code-behind (if you have any code in here). There are a few ways to debug this cryptic exception here (read the comments for more tips)
Also the Build Action of your .xaml resource file should be "Page" rather than "Resource".
Try building the solution with your newly merged dictionary before you start referencing the external styles in your xaml.
It may seem counter intuitive but it is possible for visual studio to know about a type in another xaml file without the designer being aware which can cause bugs like this.
Koda

Specifying styles in a WPF user control library

I'm having some problems sharing common styles between different projects. Here are my projects:-
"Common" - a class library containing common WPF styles.
"Plugin" - a class library containing a user control (not a custom control).
"Core app" - the core WPF application, which displays the user control defined in "Plugin".
"Common" and "Core app" reside in the same solution, while "Plugin" is in a solution of its own.
Styles in the "Core app" project work fine - it references the "Common" project and has the following in App.xaml:-
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="pack://application:,,,/Common;component/MyStyles.xaml" />
</ResourceDictionary.MergedDictionaries>
When editing XAML files in the "Core app" project, I get intellisense on style names, no squiggly underlines, and everything works fine at runtime too.
The Problem is with the "Plugin" project. I've referenced the "Common" assembly, and created a \Themes\Generic.xaml file containing the same merge XAML as above. Generic.xaml has a build action of "Page", and I've added the following to AssemblyInfo.cs:-
[assembly: ThemeInfo(ResourceDictionaryLocation.None,
ResourceDictionaryLocation.SourceAssembly)]
When I edit the XAML of a user control in this "Plugin" project, styles in the "Common" assembly don't show up in intellisense, and VS/Resharper puts a squiggly line under their names. I've even added a style directly to Generic.xaml, but the UC can't see that either. The user control looks something like this:-
<UserControl ..blah..>
<StackPanel>
<TextBlock Style="{StaticResource AStyleInCommonAssembly}" Text="Hello"/>
<TextBlock Style="{StaticResource AStyleInGenericXaml}" Text="World"/>
</StackPanel>
</UserControl>
At runtime, WPF does correctly apply styles that reside in the "Common" assembly (I guess it's finding them due to the dictionary merge in App.xaml). However it's still unable to find the style that I added directly to Generic.xaml.
What am I missing? Does this approach only work with custom controls, and not user controls as I'm dealing with? My priority is to get things working at runtime, but getting the design-time/intellisense experience would be a bonus.
Solved. It seems that the technique of using generic.xaml only applies to custom controls, not user controls. Styles defined in generic.xaml (directly, or merged from another assembly) are not accessible by a user controls.
After realising this, I just went back to merging the external assembly resource dictionary within the user control itself, i.e.:
<UserControl.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="pack://application:,,,/Common;component/MyStyles.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</UserControl.Resources>
Not ideal having to do this in each UC, but I can live with it in my scenario as I'll only ever have one or two in these "plugin" projects.
In doing this though, I did uncover another issue, and the following may help someone in the future. The MyStyles.xaml file in my "Common" assembly contains no styles of its own - it simply merges a number of other resource dictionaries in that assembly. This was done for convenience, meaning a consumer only needed to merge in MyStyles.xaml, rather than the dozen or so individual XAMLs in that assembly.
It turns out that there is a bug in WPF whereby "nested" merged dictionaries don't get parsed correctly. I found that if I put a style directly in MyStyles.xaml, the user control would find it. However it refuses to recognise any of the styles in the dictionaries that MyStyles.xaml merges! I've now dropped MyStyles.xaml, and have gone back to merging in the individual dictionaries (from within the user control XAML) - everything works, at design-time and runtime.
You can also define a UserControlBase from which all your UC derive and then merge all your styles in it.

Resources not resolving from ResourceDictionaries

I'm having problems resolving Resources from my ResourceDictionaries.
I decided to refactor my rather large ResourceDictionary into individual dictionary files, organized into subfolders.
I have a ResourceLibrary.xaml under Resources:
<ResourceDictionary x:Class="MyProject.Resources.ResourceLibrary"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<ResourceDictionary.MergedDictionaries>
<!-- Colours -->
<ResourceDictionary Source="Colors/ConnectedCellColor.xaml" />
<!-- Brushes -->
<ResourceDictionary Source="Brushes/ConnectorCellBrush.xaml" />
<!-- Control Templates -->
<ResourceDictionary Source="ControlTemplates/ConnectorCellTemplate.xaml" />
<!-- Base Styles -->
<ResourceDictionary Source="BaseStyles/ConnectorBaseStyle.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
The class is there for a reason, in the code behind, I can add [Export(typeof (ResourceDictionary))] so MEF can find it.
I have a View: (simplified)
<UserControl x:Class="MyProject.ConnectorCellView"
Style="{StaticResource ConnectorBaseStyle}"
</UserControl>
ConnectorBaseStyle:
<ResourceDictionary>
<Style x:Key="ConnectorBaseStyle" TargetType="UserControl">
<Setter Property="Template" Value="{StaticResource ConnectorCellTemplate}" />
</Style>
</ResourceDictionary>
The template has StaticResources to try and get a Brush and a colour.
All of these StaticResources will not resolve any more.
I thought it might have been an order issue, but since these resources are contained in a plugin to my main program, I use MEF and ImportMany to get all the exported ResourceDictionaries, and in my Caliburn.Micro bootstrapper:
public void OnImportsSatisfied()
{
foreach (ResourceDictionary resourceDictionary in ResourceDictionaries)
{
Application.Resources.MergedDictionaries.Add(resourceDictionary);
}
}
(Neat trick I found somewhere)
I can actually run my program, and when that view is created it throws an exception when trying to set the style:
System.InvalidCastException
Unable to cast object of type 'MS.Internal.NamedObject' to type System.Windows.FrameworkTemplate'.
The only information I've found relating to this has to do with the order resources are defined, but from the order I have them in ResourceLibrary, it should work.
When the exception is thrown, I can examine Application.Current.Resources.MergedDictionaries,
and see the resources.
I've tried various ways of specifying the Source in ResourceLibrary
<ResourceDictionary Source="/MyProject;component/Resources/BaseStyles/ConnectorBaseStyle.xaml" />
etc, no effect on finding them. These resources are only used by the plugin code.
The only thing that seemed to work was changing all the StaticResources to DynamicResources
which doesn't make sense to me, if it is an order issue, why would Static work when they're all in the same file?
Some of my styles used BasedOn, and they don't work with DynamicResource.
Can you help me understand why this is happening, and how to make it work?
It is an ordering problem but not with the ordering of your merging - it's with the order of loading. Here's basically what happens when the ResourceLibrary dictionary loads:
ConnectedCellColor instantiates
ConnectedCellColor loads into ResourceLibrary
ConnectorCellBrush instantiates
ConnectorCellBrush loads into ResourceLibrary
ConnectorCellTemplate instantiates
ConnectorCellTemplate loads into ResourceLibrary
ConnectorBaseStyle instantiates
ConnectorBaseStyle loads into ResourceLibrary
The problem here is that where before with your single file you had a single instantiation step, you now have that broken up into multiple steps, each of which happen independently. When ConnectorBaseStyle is instantiated ConnectorCellTemplate has been loaded but ResourceLibrary's contents aren't known to ConnectorBaseStyle at this point. With DynamicResource this isn't a problem because those references can just resolve at step 8, but StaticResource requires immediate resolution at step 7.
The simplest fix is to use Dynamic wherever you can. For places that require Static (like BasedOn) you need to guarantee that the resource will be available during instantiation, either by also merging, for example, ConnectorCellTemplate into ConnectorBaseStyle, or by merging everything that's needed into App.xaml which is available to everything. This can make things complicated and hard to manage as you get more files and do merging into multiple places but at least the resource system is smart enough to recognize duplicates so in the case above you would still only get a single instance of ConnectorCellTemplate even though it is being merged at two places.

How to make loose Xaml content aware of custom controls

I have a loose XAML file...
<Style
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:MyNamespace"
TargetType="{x:Type local:CustomControl}">
<Setter Property="HoverOpacity" Value="1.0"/>
</Style>
... that I want to load at runtime. When I do I get an exception stating, "Type reference cannot find public type named 'CustomControl'." How can I make the loose XAML aware of my namespace?
I need to use HoverOpacity which is a dependency property of the CustomControl. Here is the code that I am currently using to load the XAML:
var resource = Application.GetResourceStream(new Uri("pack://application:,,,/Assets/HoverStyle.xaml"));
XamlReader.Load(resource.Stream);
BTW, I realize that the XAML is simple and I could just insert the Style in code, but this is a hello world XAML; its going to become a lot more complex, involving animations and such.
P.S. Another solution would be a way of either attaching a XAML file to a custom control derived from Panel (one that doesn't crash Visual Studio 2008) or a way of easily attaching triggers, data-triggers, entry-actions, and exit actions to custom controls.
Gosh darn it, I figured it out. I needed to specify the assembly name with the namespace; like so:
<Style
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:MyNamespace;assembly=MyAssembly"
TargetType="{x:Type local:CustomControl}">
<Setter Property="HoverOpacity" Value="1.0"/>
</Style>
I'll give answer credit to anyone who could answer my "P.S." question within the next two days. This whole situation seems a little wet, so I'd be really interested in alternatives.
Thanks :)

Resources