Reputation: 33
I came across this oddity while trying to resolve issues using merged ResourceDictionaries in a WPF app I'm working on.
I have custom controls (TextButton, MenuButton) and resources (colors, brushes, control styles and custom control templates) defined in an external DLL ("common"). In another library I have a user control that uses these styles ("pluginA").
As long as I was working with the standard WPF controls (TextBlock, Button, Grid etc.) - I could apply the styles from the "common" dll without any problems. The designer would pick up the style and apply it correctly.
If I plop in one of the custom controls (TextButton) into the User Control in "pluginA" - the designer would find the custom control, but couldn't resolve the type for the style to be applied (Type reference cannot find the type named '{clr-namespace:Common}TextButton').
The xmlns declaration in my usercontrol looks like this:
<UserControl x:Class="PluginA.Views.LeftBarView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:core="clr-namespace:Common.Core;assembly=Common"
xmlns:common="clr-namespace:Common;assembly=Common"
mc:Ignorable="d"
d:DesignHeight="600" d:DesignWidth="300">
<UserControl.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<core:SharedResourceDictionary Source="/Common;component/Resources/DefaultTheme/DefaultTheme.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</UserControl.Resources>
With this definition, the designer doesn't apply any styles - but it works in runtime. Great, but not quite that helpful as I don't want to run the application to see if a minor tweak took effect.
So I tried this:
<core:SharedResourceDictionary Source="pack://application:,,,/Common;component/Resources/DefaultTheme/DefaultTheme.xaml" />
But that didn't change anything (designer still wouldn't find the resources). In the process of changing the code, I got to this:
<core:SharedResourceDictionary Source="pack:/Common;component/Resources/DefaultTheme/DefaultTheme.xaml" />
Now the designer is happy and can find the resources - runtime is happy and displays the resources, and yet I can't find any description of this being a valid PACK URI... Can anyone explain why this would work?
Upvotes: 3
Views: 4343
Reputation: 25623
pack:/Common;component/Resources/DefaultTheme/DefaultTheme.xaml
This is technically a valid URI, but it is not a valid pack
URI. Parsing it according to the rules of the pack
format would yield:
Package URI: <empty>
Part URI: /Common;component/Resources/DefaultTheme/DefaultTheme.xaml
In effect, you have made an absolute URI out of a part URI by appending the pack:
scheme. However, without a well-formed package component, the result is not a valid pack
URI. And, interestingly, the Uri
class will not actually parse the original string as an absolute URI; it is parsed incorrectly as a relative URI, and that is part of the reason it works when assigned to ResourceDictionary.Source
. Let's take a look at the property setter:
public Uri Source
{
get { return _source; }
set
{
// ...
_source = value;
Clear();
Uri uri = BindUriHelper.GetResolvedUri(_baseUri, _source);
WebRequest request = WpfWebRequestHelper.CreateRequest(uri);
// ...
}
The key lies within BindUriHelper.GetResolvedUri(_baseUri, _source)
. The logic there, which differs from much of the pack
URI handling in WPF, sees that _source
is not an absolute URI (at least according to the broken Uri
class), so it attempts to combine it with the resolved base URI, which we presume to be pack://application:,,,/
. The URIs are combined via new Uri(Uri baseUri, Uri relativeUri)
, which works only because Uri
incorrectly parsed the original string as a relative URI. The URI which ultimately gets used to create the WebRequest
is equivalent to:
new Uri(
new Uri("pack://application:,,,/"),
new Uri("pack:/Common;component/Resources/DefaultTheme/DefaultTheme.xaml"))
...which produces:
pack://application:,,,/Common;component/Resources/DefaultTheme/DefaultTheme.xaml
And viola, we end up loading the resources from a valid pack URI even though we gave it an invalid one.
We know that the "bad" URI works because it gets accidentally transformed into a good one. As to why that same "good" URI not work in the designer when it's used directly, that is very curious.
Perhaps you simply need to rebuild both the Common
project and the project that is attempting to merge the resource dictionary. If it still fails, then it's possible your UserControl.Resources
has a different BaseUri
when running in the designer than it does at runtime. Let's see if we can figure out what the BaseUri
is at design time. Modify your UserControl
as follows:
<UserControl xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
...
xmlns:m="clr-namespace:System.Windows.Markup;assembly=System.Xaml"
x:Name="Root">
<UserControl.Resources>
<ResourceDictionary">
<Style x:Key="BaseUriTextStyle" TargetType="TextBlock">
<Setter Property="Text"
Value="{Binding ElementName=Root,
Path=Resources.(m:IUriContext.BaseUri)}" />
</Style>
</ResourceDictionary>
</UserControl.Resources>
<TextBlock Style="{StaticResource BaseUriTextStyle}" />
</UserControl>
See what gets displayed in the designer. It may give us a clue.
Upvotes: 2