Including Optional Resource in WPF Window - c#

I want to be able to use either a default bitmap resource or one provided by a separate assembly in a WPF window.I thought I could do this by defining the default bitmap in the Window.Resources section, and then search for and load if found the resources from the separate optional assembly:
[xaml file for window]
<Window.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary>
<BitmapImage x:Key="J4JWizardImage" UriSource="../assets/install.png"/>
</ResourceDictionary>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Window.Resources>
[code behind for window constructor]
try
{
var resDllPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Olbert.JumpForJoy.DefaultResources.dll");
if( File.Exists( resDllPath ) )
{
var resAssembly = Assembly.LoadFile( resDllPath );
var uriText =
$"pack://application:,,,/{resAssembly.GetName().Name};component/DefaultResources.xaml";
ResourceDictionary j4jRD =
new ResourceDictionary
{
Source = new Uri( uriText )
};
Resources.Add( J4JWizardImageKey, j4jRD[ "J4JWizardImage" ] );
}
}
catch (Exception ex)
{
}
InitializeComponent();
However, the default image was always displayed, even when the separate resource assembly was present. Apparently, resources defined within a Window definition take precedence over resources added when the window is constructed.
So I removed the Window.Resources section, added a standalone resource xaml file:
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Olbert.Wix.views">
<BitmapImage x:Key="DefaultWizardImage" UriSource="../assets/install.png"/>
</ResourceDictionary>
and modified the window constructor code so that if the separate assembly wasn't found, the resource from the standalone xaml file would be added instead:
if( File.Exists( resDllPath ) )
{
// same as above
}
else
Resources.Add( J4JWizardImageKey, TryFindResource( "DefaultWizardImage" ) );
This worked when the separate assembly was present. However, it failed when the separate assembly was left out, because the default image resource was not found. That may be because this Window isn't part of a WPF app; it's the UI for a Wix bootstrapper project.
It feels like there should be a simpler solution for what I'm trying to do, which I imagine is pretty common whenever a WPF library is designed (i.e., you need some way to allow customization of bitmaps, but you also want to provide a default/fallback).

It sounds like you're only ever getting the initial value of the resource, as of when the XAML was parsed. If it's not there at that time, there's nothing; if it's a thing then, it's only ever that thing.
That is the behavior you'll see when you use StaticResource to retrieve the resources rather than DynamicResource. DynamicResource will update the target when the resource is replaced.
<Label Content="{DynamicResource MyImageSomewhere}" />

Related

Accessing a DynamicResource in code-behind [duplicate]

I have a DataTemplate defined in a xaml file that I want to access via C# code.
Can anyone please tell me how can I access it?
I added a new ResourceDictionary file and its name is Dictionary1.xaml.
I have a data template such as:
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<DataTemplate x:Key="mytemplate">
<TextBlock Text="Name:" Background="Blue"/>
</DataTemplate>
</ResourceDictionary>
not I have a ListBox called listBox1 and I want to assign it to it's Itemtemplate property
but I'm not getting how can i do it?
Since Application.Current was null in my case, I've ended up using this:
var myResourceDictionary = new ResourceDictionary();
myResourceDictionary.Source =
new Uri("/DllName;component/Resources/MyResourceDictionary.xaml",
UriKind.RelativeOrAbsolute);
and then getting the specified key I needed by using
myResourceDictionary["KeyName"] as TypeOfItem
(source)
Where exactly are you defining it?
If you define it in the ResourceDictionary of your object, then
Application.Current.Resources[typeof(yourDataTemplateTargetType)]
should work. If you are defining it as a member of something else, like say, an ItemsControl, you need to get a handle to the ItemsControl instance and call the ItemTemplate property.
Edit: Ok, I think we're getting somewhere. So you are defining a ResourceDictionary in its own file. Before you can use it in your UI and access it from your code behind, you need to merge that ResourceDictionary into your application. Are you doing this?
If you are, then the next step is to get this resource. Each FrameworkElement has a method called FindResource. This method is great because it walks up the ResourceDictionary tree and attempts to locate the resource with the key. So, if you want to access this resource from a UserControl, you can do the following in the code behind:
FindResource(typeof(yourDataTemplateTargetType));
If this doesn't work for you, please show us exactly how you are declaring this resource dictionary and how it is getting merged into your application's resources.
If you for example have a template for Button in your resource dictionary in the App.xaml file you can access it using the following code:
Application.Current.Resources[typeof(Button)]
If you have merged resource dictionary using code like below
<Window x:Class="MainWindow">
<Window.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="DefaultStyle.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Window.Resources>
</Window>
Then, instead of Application.Current.Resources["ResourceKey"] you need to specify Control name (in this case MainWindow) also like below
var style = Application.Current.MainWindow.Resources["ResourceKey"];
// OR
var style = Application.Current.MainWindow.TryFindResource("ResourceKey");
If you're getting the resources within the same project, try this:
yourControl.Style = FindResource("YourResourceKey") as Style;
Otherwise, try this:
ResourceDictionary res = (ResourceDictionary)Application.LoadComponent(new Uri("/ProjectName;component/FolderName/ResourceDictionaryName.xaml", UriKind.Relative));
yourControl.Style = (Style)res["YourResourceKey"];
You can access a resource dictionary you added to your project as follows:
var rd = new ResourceDictionary();
rd.Source = new Uri("ms-appx:///Dictionary1.xaml");
Then you can access a resource stored in the resource dictionary like so:
someObject.Property = rd["mytemplate"];
NOTE:
You will have to modify the URI to the resource dictionary according to the location you created it relative to the project's base directory.
I found the answer here
https://learn.microsoft.com/en-us/dotnet/framework/wpf/advanced/how-to-use-a-resourcedictionary-to-manage-localizable-string-resources
create a ressource dictionary "ColorResources.xaml"
add to it:
Blue
edit your app.xml and add:
use the color from your code
var color = (System.Windows.Media.Color)Application.Current.FindResource("ButtonColor1");
and voilĂ 
ps : admin can you fix the code? it does not show up, thanks
Any of the above approaches work getting the resource based on the location, if you are following MVVMm I would recommend doing it this way:
create a Service like ProvideDataTemplateService, (to create a service usual inherit from Behavior )
Use Container of Your choice to inject this service where you would like to have aces to DataTemple.
For the life of me, although I was able to load my resource dictionary via XAML, I wasn't able to load it via "code behind" (in C#).
So I resorted to have a view loading it: (MyView.xaml)
<UserControl.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="/My.Proj;component/My/Path/myResourceDictionary.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</UserControl.Resources>
Then access it in my UT by instanciating that view and accessing it:
new MyView().Resources.MergedDictionaries[0]
Hacky, but works.
Just to add another answer here in case you don't have a view or Application.Current is null. I realize this is probably uncommon but in my case I have an addin to a parent application and Application.Current is null; I also want to pass one of my resources to the parent as an ImageSource so I don't have a XAML view created to get resources from directly.
You can also make the dictionary into a code behind creatable object. Just set the x:Class attribute in the XAML and then create a .xaml.cs file in the code behind. Your updated XAML, lets call the code file MyDictionary.xaml, would then look something like this:
<ResourceDictionary 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"
x:Class="Some.Namespace.MyDictionary"
mc:Ignorable="d">
...Resources...
</ResourceDictionary>
And the code behind (MyDictionary.xaml.cs) would look something like this:
using System.Windows;
namespace Some.Namespace
{
public partial class MyDictionary : ResourceDictionary
{
public MyDictionary()
{
InitializeComponent();
}
}
}
Don't forget to call InitializeComponent() as that's what loads the resources. Actually not sorry, see edit below
After you do this you can simply construct an instance of the class anywhere in code and reference the resources by key like this:
var dictionary = new MyDictionary();
var resource = dictionary["whateverKey"] as WhateverResourceType;
Thanks to this post for leading to the idea.
EDIT
Just ran into one potential issue with this. I got a 'Cannot re-initialize ResourceDictionary instance' exception with this setup on some of my controls. On further research this could be related to calling InitializeComponent in the constructor. Instead I removed the constructor from the code behind and added a static method to get an initialized instance as follows:
public static MyDictionary ConstructInitializedInstance()
{
var dictionary = new MyDictionary();
dictionary.InitializeComponent();
return dictionary;
}
You could also just create and initialize in your code behind.

Sharing WPF-Dictionary from another assembly

Ok, I busting my head on this for few hours now and still cannot find a solution.
first I shall explain the simple test case I created:
Solution
- ClassLibrary1
- Dictionary1.xaml
- WpfApplication3
- App.config
- App.xaml
- Dictionary2.xaml
- MainWindows.xaml
ClassLibrary1:
That project has the required references to allow me to add wpf-dictionary:
PresentationCore, PresentationFramework, Systam.Xaml, windowsbase
(Along with all standard assemblies for any regular class library)
And this is Dictionary1.xaml:
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Color x:Key="PrimaryBackgroundColor">#FF030010</Color>
<SolidColorBrush x:Key="PrimaryBackgroundBrush" Color="{StaticResource PrimaryBackgroundColor}" />
</ResourceDictionary>
WpfApplication3:
This project just display a button on a wpf-form.
Dictionary2.xaml:
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication3">
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="pack://application:,,,/ClassLibrary1.dll;component/Dictionary1.xaml"/>
</ResourceDictionary.MergedDictionaries>
<Style TargetType="Button">
<Setter Property="Background" Value="{StaticResource PrimaryBackgroundBrush}" />
</Style>
</ResourceDictionary>
MainWindow.xaml:
<Window x:Class="WpfApplication3.MainWindow"
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:local="clr-namespace:WpfApplication3"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Dictionary2.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Window.Resources>
<Grid>
<Button Content="aaa" Width="100" Height="40" />
</Grid>
</Window>
That's all - very simple as you can see.
The only thing here is that dictionary2 need to use resource from dictionary1.
And so there are two ways to reference another assembly:
Option 1:
The class-library is a project in your solution and your WpfApplication adds reference to the class library project which is in the same solution. this is done via Add-Reference/Projects, And in that situation all works great.
Option 2:
The class-library is not your solution. (actually it can be like in my example)
however you add reference by adding reference to ClassLibrary1.Dll which resides either in your
bin\debug or bin\release folders.
In that situation a portal to hell is opened.
Dictionary2 complains it cannot find the resource 'PrimaryBackgroungBrush' and upon execution it crush
complaining it cannot find the dictionary1.xaml
Exception thrown: 'System.Windows.Markup.XamlParseException' in PresentationFramework.dll
and the inner exception:
{"Could not load file or assembly 'ClassLibrary1.dll, Culture=neutral' or one of its dependencies.
The system cannot find the file specified.":"ClassLibrary1.dll, Culture=neutral"}
The problem is that using option2 is essential as I want to share the same dictionary among other wpf projects without
having the ClassLibrary1 project as part of their solution.
Suggested way to reproduce:
Create a new solution in Visual studio for WPF application.
Add class library project to the solution.
In class libarary project, Add references to the following assemblies: PresentationCore, PresentationFramework, Systam.Xaml, windowsbase
Add Wpf-Dictionary 'Dictionary1' to your class library project and copy the code. (you can copy one from the wpf project since it will not exist as an option in the add item from the class library)
Add Wpf-Dictionary 'Dictionary2' to your wpf application and copy the code.
Copy the code for MainWindow.
And now:
Add reference to class library (as project, from projects tab in add refernce dialog)
Build everything - all should work.
Remove the refernce to class library.
Add reference to class library (as dll, from browse tab and find it in your classlibrary/bin/debug or release folder)
Build everything - you will notice my problem.
Any solution to this problem?
UPDATE 1
I changed the line in dictionary2.xaml from:
<ResourceDictionary Source="pack://application:,,,/ClassLibrary1.dll;component/Dictionary1.xaml"/>
To:
<ResourceDictionary Source="pack://application:,,,/ClassLibrary1;component/Dictionary1.xaml"/>
And now the project compiles and execute without an error, However while in design time - the xaml view of dictionary2 indicate that it cannot find the resource: 'PrimaryBackgroundBrush` and puts the ugly curly underline below it.
So its a progress - but i'm still not happy with that.
Any ideas how to solve that?
UPDATE 2
As previously stated - everything compiles and execute now.
However what you see in the following picture annoys me,
I just want to be sure that others who added the class library as .Dll file and not as project 100% sure they don't get that problem which can be seen in the picture, meaning their xaml intellisense can recognize the resource during design time.
I could imagine how documentation about that dll will looks like:
reference dll in the project
add this to resource dictionary in the project:
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="pack://application:,,,/ClassLibrary1.dll;component/Dictionary1.xaml"/>
</ResourceDictionary.MergedDictionaries>
add this to each window/usercontrol:
<Window.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Dictionary2.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Window.Resources>
Which looks afwul.
How about making manager in your library which has to be referenced by each window/usercontrol and it will do things automatically?
Here is a cut from theme manager I mentioned in comments (it does merging automatically), think about easy of use.
xaml (add this to each window/usercontrol which has to support theme switching in design/run time):
local:Theme.Theme=""
cs (this part has to be a part of library):
public static class Theme
{
public static readonly DependencyProperty ThemeProperty =
DependencyProperty.RegisterAttached("Theme", typeof(string), typeof(Theme), new PropertyMetadata(null, (d, e) =>
{
var theme = (string)e.NewValue;
// in run-time set theme to specified during init
if (!DesignerProperties.GetIsInDesignMode(d))
theme = _theme;
var element = d as FrameworkElement;
element.Resources.MergedDictionaries.Clear();
if (!string.IsNullOrEmpty(theme))
{
var uri = new Uri($"/MyPorject;component/Themes/{theme}.xaml", UriKind.Relative);
element.Resources.MergedDictionaries.Add(new ResourceDictionary() { Source = uri });
}
}));
public static string GetTheme(DependencyObject obj) => (string)obj.GetValue(ThemeProperty);
public static void SetTheme(DependencyObject obj, string value) => obj.SetValue(ThemeProperty, value);
static string _theme = "Generic";
static string[] _themes = new[]
{
"Test",
};
/// <summary>
/// Init themes
/// </summary>
/// <param name="theme">Theme to use</param>
public static void Init(string theme)
{
if (_themes.Contains(theme))
_theme = theme;
}
}
P.S.: functionality is primitive (it is sufficient in my case), but should give you an idea.

XamlParseException when attempting to open a WPF Window from Winforms

I have a single Winforms project and multiple WPF projects in one solution. From the Winform application I'd like to open one of the WPF Windows (it's a MetroWindow from Mahapps, if it matters).
After looking at the accepted answer to this stackoverflow question I ended up with this piece of code:
OpenWPFAppButton_Click(object sender, EventArgs e)
{
WPFApp wpfApp = new WPFApp();
ElementHost.EnableModelessKeyboardInterop(wpfApp);
wpfApp.Show();
}
Unfortunately if I click the button a XamlParseException occurs, which points to the first Style="{StaticResource ... }" line in WPFApp.xaml (the Main Xaml File).
Does this mean I cannot open WPF Windows from Winforms that include static resources? Or am I missing something simple here?
EDIT: Here is the content of the App.xaml file:
<Application x:Class="WPFAppProjectName.App"
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"
StartupUri="WPFApp.xaml">
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="pack://application:,,,/Selen.Wpf.SystemStyles;component/ButtonStyles.xaml"/>
<ResourceDictionary Source="pack://application:,,,/Selen.Wpf.SystemStyles;component/MenuStyles.xaml"/>
<ResourceDictionary Source="pack://application:,,,/Selen.Wpf.SystemStyles;component/TextBoxStyles.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
It is most likely that some common resources are defined in the App.xaml. That file isn't loaded when running through Windows Forms and thus those resources are unavailable. Hence you get this error.
You could include the resource definitions in the Window.xaml files, or an own common style resource file (aka Resource Dictionary) which is included in every Window.

WPF Designer Failing to Load Generic.xaml

I have written a WPF Class Library. It has a Generic.xaml file under a themes folder in the project and also a ThemeInfo attribute in the AssemblyInfo.cs file:
[assembly: ThemeInfo(
ResourceDictionaryLocation.None,
ResourceDictionaryLocation.SourceAssembly)]
All works well, except in the designer. I get blue squigly lines anywhere I am using StaticResource to reference my brushes, styles and other resources saying:
The resource '[Resource Name]' cannot be found.
I really want the designer to pick up my Generic.xaml file and show the controls as I have styled them. How can I achieve this?
UPDATE
I have marked Yogesh's answer as correct but here is some more information. I was adding the resource dictionary in the constructor of the App.xaml file, instead of in the xaml. The XAML designer does not seem to execute the code behind for the App.xaml file.
Just add a new page named App.xaml with Application as the root element in the class library with Build Action set to Page. Now add the generic.xaml file as a resource dictionary. Something like this...
<Application x:Class="[YourNamespace].App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="[AbsoluteOrRelativePath]/Generic.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
</Application>
After you do this, rebuild your project, close all xaml views and reopen them again. This should fix your issue in VS2012/2013 and Blend 2012.

Have default string resource file and override it with custom ones

So the title is perhaps not completly clear.
I have a Strings.xaml file which contains several strings which are used in the application.
Strings.xaml
<!-- GENERAL FOR ALL TESTS -->
<my:String x:Key="AppTitle">AppName</my:String>
<my:String x:Key="TestName1">test_1</my:String>
<my:String x:Key="TestName2">test_2</my:String>
<!-- DEFAULT MESSAGES -->
<my:String x:Key="TestMessage">This is a default message</my:String>
<my:String x:Key="TestDescription">This is a default description</my:String>
<my:String x:Key="OnlyCustomInTest2">This string is used as a default message if not overridden by custom resource file</my:String>
</ResourceDictionary>
This resource file works great. What I'm wondering is if there is any built in way that I can use Strings.xaml as a default resource file and then override specific strings that are custom for different program modes? Like having Strings.xaml default and use Test_1_Strings.xaml and Test_2_Strings.xaml to override some strings for custom messages.
Test_1_Strings.xaml
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:my="clr-namespace:System;assembly=mscorlib">
<!-- CUSTOM FOR TEST 1 -->
<my:String x:Key="TestMessage">This is a message for test 1</my:String>
<my:String x:Key="TestDescription">This is a description for test 2</my:String>
</ResourceDictionary>
Test_2_Strings.xaml
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:my="clr-namespace:System;assembly=mscorlib">
<!-- CUSTOM FOR TEST 2 -->
<my:String x:Key="TestMessage">This is a message for test 2</my:String>
<my:String x:Key="TestDescription">This is a description for test 2</my:String>
<my:String x:Key="OnlyCustomInTest2">This is the overridden message for test 2</my:String>
</ResourceDictionary>
The reason I want to do this is because I have many different program modes where most of the resources are the same but some are custom. Instead of having to change a shared entry in 8 different resource files I could do it in only one place.
Resource lookup in WPF traverses from bottom to top i.e. any resource usage will first look for resource in its parent container which can be Grid, StackPanel etc. If not found in parent container will look for resource in parent's parent container and so on to UserControl, Window till it reach App resources.
Also, any resource later defined under resources section overrides the resource added previously with same key. This is true for resources defined under different resource dictionaries but not within the same XAML file. If you try to declare two items with same key, it will fail with key already exists exception.
You can take advantage of above stated features to your use.
Assuming you are merging the resources under App resources, what you can do is add Strings.xaml at top and then add other resource dictionaries Test_1_Strings.xaml and Test_2_Strings.xaml. This way resources with same name will be overridden and resource defined at last will always be resolved.
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Strings.xaml"/>
<ResourceDictionary Source="Test_1_Strings.xaml"/>
<ResourceDictionary Source="Test_2_Strings.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
So when you declare TextBlock to refer to StaticResource TestMessage.
<TextBlock Text="{StaticResource TestMessage}"/>
it will print This is a message for test 2.
If you change the order and add Test_1 after Test_2, textBlock Text will be - This is a message for test 1.

Categories

Resources