Using Custom Resource in DevExpress WPF Theme - c#

Objective:
I have a WPF project which shall be themed using DevExpress Themes.
There is a Login-UserControl that shall have a themable background image.
Implementation
I made a custom Theme. In that theme I have a Folder "CustomResources" in which there is an Image, let's call it "Background.png" and a "Brushes.xaml" that defines an ImageBrush like this:
<ResourceDictionary ...>
<ImageBrush x:Key="{CustomThemeKeyAssembly:CustomThemeResourcesThemeKey ResourceKey=LoginBackgroundImageBrush, ThemeName=CustomTheme}" ImageSource="Background.png" />
</ResourceDictionary>
Accordingly, I have a shared Assembly CustomThemeKeyAssembly that derives a Custom ResourceThemeKey.
In the Project, I register and set the Theme using ApplicationThemeHelper
var theme = new Theme("CustomTheme")
{
AssemblyName = "DevExpress.Xpf.Themes.CustomTheme.v17.2"
};
Theme.RegisterTheme(theme);
ApplicationThemeHelper.ApplicationThemeName = "CustomTheme";
and I reference the Resource through
Background="{dxci:ThemeResource ThemeKey={CustomThemeKeyAssembly:CustomThemeResourcesThemeKey ResourceKey=LoginBackgroundImageBrush}}"
As advised by DevExpress Knowledgebase / Support.
Problem
The Resource is only found and displayed, if I add a Merged Resource Dictionary like this:
ResourceDictionary loginBackgroundDictionary = new ResourceDictionary
{
Source = new Uri($"pack://application:,,,/{MyProject.Properties.Settings.Default.ThemeAssembly};Component/CustomResources/Brushes.xaml", UriKind.Absolute)
};
//Add LoginBackgroundImageBrush Dictionary
Resources.MergedDictionaries.Add(loginBackgroundDictionary);
No article or example mentions having to do this, though. So my impression is that I either am doing something wrong or I am missing some simple step like merging the Brushes.xaml into some ResourceDictionary.
Without that snippet I get a warning that the resource could not be found.
Question
Has anybody an idea where I am going wrong or what I am missing to get this working without that last snippet?
FYI: I am using DevExpress 17.2.3 and the ResourceKey Assembly is targeted to .net Framework 4.0
EDIT
Meanwhile, I tried adding the Brushes.xaml to Themes/Generic.xaml in the theme assembly like this:
<ResourceDictionary.MergedDictionaries>
<dxt:ResourceDictionaryEx Source="/DevExpress.Xpf.Themes.Office2016WhiteSE.v17.2;component/Themes/ControlStyles.xaml" />
<dxt:ResourceDictionaryEx Source="/DevExpress.Xpf.Themes.Office2016WhiteSE.v17.2;component/CustomResources/Brushes.xaml" />
</ResourceDictionary.MergedDictionaries>
It didn't make any difference. Same behavior as before.

Problem solved!
The problem was in the CustomThemeKeyAssembly
The wrong implementation was
public class CustomThemeResourcesThemeKey : ThemeKeyExtensionBase
{
public override Assembly Assembly => TypeInTargetAssembly != null ? TypeInTargetAssembly.Assembly : GetType().Assembly;
}
The working implementation is
public class CustomThemeResourcesThemeKey : ThemeKeyExtensionBase<ThemeResourcesThemeKeys> { }
The breaking difference is the override of the Assembly property. The default implementation makes it work. I did that because it was done so in an example. Support told me to stick with the default implementation and it worked.

Related

Where to Load ContentControl Styles in Avalonia Class Library?

I have a ContentControl/TemplatedControl written in Avalonia Class Library, and the styles defined in a file.
To load the styles, in WPF, you'd need to add AssemblyInfo.cs with this hack
using System.Windows;
[assembly: ThemeInfo(
ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
//(used if a resource is not found in the page,
// or application resource dictionaries)
ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
//(used if a resource is not found in the page,
// app, or any theme specific resource dictionaries)
)]
Now with Avalonia... what's the way to do it?
EDIT: Is the answer that the client must register the files manually in App.xaml?
<Application.Styles>
<StyleInclude Source="avares://Avalonia.Themes.Default/DefaultTheme.xaml"/>
<StyleInclude Source="avares://Avalonia.Themes.Default/Accents/BaseLight.xaml"/>
<StyleInclude Source="avares://Avalonia.Controls.DataGrid/Themes/Default.xaml"/>
</Application.Styles>
but then -- what if I want to display multiple such controls with different styles? I could have a property on the control to choose the theme or the colors.
The styles defined in the App.xaml are global, so all controls will be using them. However, it is possible to change them at runtime. In your case you could start with creating a styles dictionary to simplify things and add all those StyleInclude there, so your Application.Styles have only one entry:
<Application.Styles>
<StyleInclude Source="avares://YourAssembly/Path/YourStyles1.xaml"/>
</Application.Styles>
Now, let's say you want to change this resource to YourStyles2.xaml in the code.
private static StyleInclude CreateStyle(string url)
{
var self = new Uri("resm:Styles?assembly=YourAssembly");
return new StyleInclude(self)
{
Source = new Uri(url)
};
}
private void SetStyles()
{
var newStyles = CreateStyle("avares://YourAssembly/Path/YourStyles2.xaml");
Avalonia.Application.Current.Styles[0] = newStyles;
}

WPF ToastNotifications from wpf class library does not load Resources

I am developing a plugin and I want to use this notification toast library from nuget: Toast Notifications
One of the steps I have to do to setup the Toast Notifications library is this:
2 Import ToastNotifications.Messages theme in App.xaml
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="pack://application:,,,/ToastNotifications.Messages;component/Themes/Default.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
However, I don't have an Application file since I am developing a class library. I have added the resourcer Dictionary to the UserControl that is creating the notifications. Like this:
<UserControl.Resources>
<ResourceDictionary Source="pack://application:,,,/ToastNotifications.Messages;component/Themes/Default.xaml" />
</UserControl.Resources>
However, when I run the App I've got this exception:
{System.Windows.Markup.XamlParseException: Provide value on 'System.Windows.StaticResourceExtension' threw an exception. ---> System.Exception: Cannot find resource named 'InformationIcon'. Resource names are case sensitive.
But InformationIcon is an image inside the ToastNotifications library. What am I doing wrong?
Your problem is the main application that uses your plugin does not have a reference to the nuget package. So, you have two methods to solve this:
If you have access to the main application's project add the nuget lib to it.
If you do not. You have to load the assembly dynamically at runtime. Here is a good solution How to reference a DLL on runtime?
It searches through dlls in a given directory and finds classes that implement a particular interface. Below is the class I used to do this:
public class PlugInFactory<T>
{
public T CreatePlugin(string path)
{
foreach (string file in Directory.GetFiles(path, "*.dll"))
{
foreach (Type assemblyType in Assembly.LoadFrom(file).GetTypes())
{
Type interfaceType = assemblyType.GetInterface(typeof(T).FullName);
if (interfaceType != null)
{
return (T)Activator.CreateInstance(assemblyType);
}
}
}
return default(T);
}
}
All you have to do is initialize this class with something like this:
PlugInFactory<InterfaceToSearchFor> loader = new PlugInFactory<InterfaceToSearchFor>();
InterfaceToSearchFor instanceOfInterface = loader.CreatePlugin(AppDomain.CurrentDomain.BaseDirectory);

WPF/XAML: How to reference class that is not defined within any namespace

I'm executing a roslyn script that tries to define and open a WPF window.
Amongst other things, my script
defines an attached behavior
defines a XAML string, based on which I create a WPF Window. In this XAML code, I'd like to use the TextBoxCursorPositionBehavior defined in my script.
my script (.csx) file looks similar to
public class TextBoxCursorPositionBehavior : DependencyObject
{
// see http://stackoverflow.com/questions/28233878/how-to-bind-to-caretindex-aka-curser-position-of-an-textbox
}
public class MyGui
{
public void Show()
{
string xaml = File.ReadAllText(#"GUI_Definition.xaml");
using (var sr = ToStream(xaml))
{
System.Windows.Markup.ParserContext parserContext = new System.Windows.Markup.ParserContext();
parserContext.XmlnsDictionary.Add( "", "http://schemas.microsoft.com/winfx/2006/xaml/presentation" );
parserContext.XmlnsDictionary.Add( "x", "http://schemas.microsoft.com/winfx/2006/xaml" );
parserContext.XmlnsDictionary.Add("i","clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity");
// ?? How can i define this properly?
parserContext.XmlnsDictionary.Add("behaviors", "clr-namespace:;assembly=" + typeof(TextBoxCursorPositionBehavior).Assembly.FullName);
var window = (System.Windows.Window)XamlReader.Load(sr, parserContext);
window.ShowDialog();
}
}
}
and assume the GUI_Definition.xaml looks like
<Window x:Class="System.Windows.Window" Height="300" Width="300" >
<Grid>
<!-- how can i attach my behavior here properly? -->
<TextBox behaviors:TextBoxCursorPositionBehavior.TrackCaretIndex="True"/>
</Grid>
</Window>
But the problem is, how can I reference TextBoxCursorPositionBehavior correctly in XAML?
Roslyn doesn't allow to use namespaces in script files, so TextBoxCursorPositionBehavior must be defined outstide of a namespace (i.e. I suppose it will fall into the global namespace).
But then, how can I reference it in XAML? I've tried defining the namespace reference with "clr-namespace:;assembly=" + typeof(TextBoxCursorPositionBehavior).ToString(), but that doesn't work.
Simply "clr-namespace:" (i.e. without assembly reference) doesn't work either.
Is there any way to reference TextBoxCursorPositionBehavior from within the XAML definition?
In your code instead of assembly you use:
typeof(TextBoxCursorPositionBehavior).ToString()
This is not an assembly name. Change it to:
parserContext.XmlnsDictionary.Add("behaviors", "clr-namespace:;assembly=" + Assembly.GetExecutingAssembly().FullName);
And it should work fine (at least works for me, but I don't test with Roslyn script but just regular WPF application).
I think I know what's happening ... Roslyn creates a custom Submission type for scripts, and seems everything - including the definition of TextBoxCursorPointerBehavior - is a sub-class of this submission type. I.e.,
var inst = new TextBoxCursorPositionBehavior();
string typeName = inst.GetType().FullName;
typeName will not be "TextBoxCursorPointerBehavior", but rather "Submission#0+TextBoxCursorPositionBehavior".
At the same time, I can NOT reference this from XAML (e.g. by behaviors:Submission#0+TextBoxCursorPositionBehavior.TrackCaretIndex="True") as it won't parse the name correctly (# is an invalid token there).
In theory, it might be possible to rename Roslyn's submission type to something that is actually referencable via XAML - in my case though, I cannot do that.
Which unfortunately currently means I don't see any solution to my issue, other than possibly outsourcing this code to a separate pre-compiled DLL (but that's not quite the point of scripting either)

The tag 'ViewModelLocator' does not exist in XML namespace clr-namespace:XXX

I have tried numerous other solutions without any success. I have a class called ViewModelLocator which is located in my portable class library. It has a property in it called ViewModels, which is of type Dictionay<K, V>
Then I have a Windows Phone 8 project that references the portable class library. I added the following to the WP8 app.xaml:
<Application
x:Class="Kaizen.WP8.Test.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"
xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone"
xmlns:test="clr-namespace:Foo.Core.Portable.ViewModel;assembly=Foo.Core.Portable">
<Application.Resources>
<test:ViewModelLocator x:Key="ViewModelLocator">
<test:ViewModelLocator.ViewModels>
<test:SampleViewModel x:Key="sampleVM"/>
</test:ViewModelLocator.ViewModels>
</test:ViewModelLocator>
</Application.Resources>
</Application>
When I press F12 on the tags, it navigates to the correct class and or property in my pcl. Which indicates that VS knows about the objects, but when I try and build, I receive the following error:
The tag 'ViewModelLocator' does not exist in XML namespace
'clr-namespace:Foo.Core.Portable.ViewModel;assembly=Foo.Core.Portable'.
The tag 'SampleViewModel' does not exist in XML namespace
'clr-namespace:Foo.Core.Portable.ViewModel;assembly=Foo.Core.Portable'.
Could anyone please provide some assistance?
[Update]
I reference the pcl version of mvvm light in my pcl project. This is how the ViewModelLocator class looks like:
public class ViewModelLocator
{
public dynamic this[string viewModelName]
{
get
{
if (this.ViewModels.ContainsKey(viewModelName))
{
return this.ViewModels[viewModelName];
}
else
{
return null;
}
}
}
public Dictionary<string, ViewModelBase> ViewModels { get; set; }
public ViewModelLocator()
{
this.ViewModels = new Dictionary<string, ViewModelBase>();
}
}
My WP8 project also makes use of the mvvm light pcl assemblies. I noticed that, if I make use of the ViewModelBase class as the dictionary value, that when I get the errors. It's as there's an issue using the mvvm light pcl between the two projects?!
[Update]
Many thanks in advance!!
Kind regards,
I just had this problem with a .Net 4.5 project.
The solution for me was to change to .Net 4.0, ignore the warnings, and change back to .Net 4.5.
Then the problem was gone.
Don't know if it is a feasible way for others, but it worked for me.
Best regards.
Okay, so I'm not exactly sure what I did wrong in my first attempt, but I recreated the solution and performed more or less the same steps and I didn't receive the error again?! o_O
I know this is a bit late but I had the same problem with a WPF Desktop app and a control library. The library's default Target Framework was .Net 4 but the Desktop app just after I created in in Visual Studio was by default created with .Net 4 client profile. I changed the Desktop app from .Net 4 client profile to .Net 4 and it worked.

How to use resource dictionary in prism modules at design time?

I am using prism framework in a silverlight app with multiple modules in separate XAPs.
I have a resource dictionary defined in my in my shell project. In my modules I can use the resources fine, but since the modules are decoupled from the shell until they are loaded at runtime the designer does not show them or recognize them.
Is there a way to make the modules aware of my resources at design time without merging my resource file in every view xaml?
My resource files are in a "common" project.
I think I have definitely solution for design-time resources.
Benefits:
It works in any module based (MEF, UNITY..) application.
It works in any designer (Visual Studio, Blend..)
It does not create multiple instances of the same ResourceDictionary
Let's consider following solution:
MyApp.Shell (.exe)
MyApp.Module1 (.dll) - loaded at runtime using MEF
MyApp.Module2 (.dll) - loaded at runtime using MEF
MyApp.Common (.dll) - referenced by all projects
you can define brushes, implicit styles, templates etc in MyApp.Common.
use my SharedResourceDictionary to include the ResourceDictionary in all projects. At design-time it will load the ResourceDictionary for each designer, at runtime the ResourceDictionary will be loaded only when necessary.
Usage example:
include SharedResourceDictionary in App.xaml
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<common:SharedResourceDictionary SharedSource="MyApp.Common;component/CommonResources.xaml" />
</ResourceDictionary>
</Application.Resources>
include SharedResourceDictionary everywhere designer fails to find
some share resource, e.g. in MyApp.Module1/UserControl1.xaml
<UserControl.Resources>
<common:SharedResourceDictionary SharedSource="MyApp.Common;component/CommonResources.xaml" />
</UserControl.Resources>
Source:
/// <summary>
/// Loads singleton instance of ResourceDictionary to current scope;
/// </summary>
public class SharedResourceDictionary : ResourceDictionary
{
/// <summary>
/// store weak references to loaded ResourceDictionary, to ensure that ResourceDictionary won't be instanciated multiple times
/// </summary>
protected static Dictionary<string, WeakReference> SharedResources = new Dictionary<string, WeakReference>();
public string SharedSource
{
get { return _SharedSource; }
set
{
if (_SharedSource != value)
{
_SharedSource = value;
sharedSourceChanged();
}
}
}
private string _SharedSource;
private void sharedSourceChanged()
{
//ResourceDictionary will be instanciated only once
ResourceDictionary sharedResourceDictionary;
lock (SharedResources)
{
WeakReference weakResourceDictionary = null;
if (SharedResources.ContainsKey(_SharedSource))
{
weakResourceDictionary = SharedResources[_SharedSource];
}
else
{
SharedResources.Add(_SharedSource, null);
}
if (weakResourceDictionary == null || !weakResourceDictionary.IsAlive) //load ResourceDictionary or get reference to exiting
{
sharedResourceDictionary = (ResourceDictionary)Application.LoadComponent(new Uri(_SharedSource, UriKind.Relative));
weakResourceDictionary = new WeakReference(sharedResourceDictionary);
}
else
{
sharedResourceDictionary = (ResourceDictionary)weakResourceDictionary.Target;
}
SharedResources[_SharedSource] = weakResourceDictionary;
}
if (Application.Current != null)
{
//if sharedResourceDictionary is defined in application scope do not add it to again to current scope
if (containsResourceDictionary(Application.Current.Resources, sharedResourceDictionary))
{
return;
}
}
this.MergedDictionaries.Add(sharedResourceDictionary);
}
private bool containsResourceDictionary(ResourceDictionary scope, ResourceDictionary rs)
{
foreach (var subScope in scope.MergedDictionaries)
{
if (subScope == rs) return true;
if (containsResourceDictionary(subScope, rs)) return true;
}
return false;
}
}
I have found there are a couple of solutions to this:
1) When you create a module project, leave the App.xaml in the project instead of deleting it and instantiate your resources in there just as if it were its own application by itself (you can also add a new Application class to the project if you have already deleted it). When your module is loaded into the shell that file will be ignored so it's essentially only valid during design time. This works well in visual studio and blend although if you have many modules, memory footprint may become a problem.
2) Using design time resources. Some info about setting this up here: http://adamkinney.com/blog/2010/05/04/design-time-resources-in-expression-blend-4-rc/. This offers only blend support and your views will be stripped of all styles and formatting in visual studio. This was not ideal for me because I like working on certain aspects of the UI in visual studio. There also doesn't seem to be a documented way of manually setting up design time resources.
Small own-experience guide for migrating resources from Shell to shared essembly and making designer work just fine
Some thoughts based on reading such questions and searching internet on the same/similar problem. I'm writing this primarily because of problem 2 (below), which is related to this issue, IMHO.
So, we had the same design, all styles and resources were in Shell. This produced 2 problems:
Context help in XAML-Editor was not available (<- resources not
found)
Designer wouldn't show up properly (<- resources not
found)
So we migrated all styles to shared assembly (Resources).
To solve the first problem you would need sth like Liero proposed, i.e. add resource dictionary to each UserControl. I didn't try his SharedDictionary, but normal ResourceDictionary definitely brings context help back and removes blue-underscore lines. Designer however still didn't show up properly.
So the second problem. There is a small trick to bring styles to designer at design time only described in this article. Basically you add a resource dictionary named DesignTimeResources.xaml to your project that contains reference to your resources:
<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:,,,/Resources;component/Themes/Generic.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
Than move it to Properties folder. Than edit manually project file and change the item for this file to this:
<Page Include="Properties\DesignTimeResources.xaml" Condition="'$(DesignTime)'=='true' OR ('$(SolutionPath)'!='' AND Exists('$(SolutionPath)') AND '$(BuildingInsideVisualStudio)'!='true' AND '$(BuildingInsideExpressionBlend)'!='true')">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
<ContainsDesignTimeResources>true</ContainsDesignTimeResources>
</Page>
Basically it's a file that Blend would generate if you add design time resources. VS cannot create it, although can read it just fine. The editing of project file says that you don't want basically this file in release.
Two minor gotchas here also, perhaps it will help somebody.
When migrating resources from Shell to Resources, our Resources project won't build with weird errors that it cannot find UserControls referenced from style files (all problematic controls were defined in the Resources project as well). They were working just fine when referenced from Shell before. The problem was that some tools (like Resharper) automatically reference these controls in namespace like "clr-namespace:XXX;assembly=Resources". The ";assembly=Resources"-part you should delete, as it is the same assembly now.
We already head some local resources in our UserControls, like this:
<UserControl.Resources>
<PresentationHelpers:BoolToVisibilityConverter x:Key="boolToVisibilityConverter" />
</UserControl.Resources>
So at first I just added new ResourceDictionary into this block, which asked me to provide an x:Key. I was so used to add resources directly to UserControl.Resources, that I didn't first realise that in order to merge another dictionary you would need <ResourceDictionary> tag that normally you could skip. So it will look like this:
<UserControl.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<Helpers:RedbexResourceDictionary Source="pack://application:,,,/Resources;component/Themes/Generic.xaml" />
</ResourceDictionary.MergedDictionaries>
<PresentationHelpers:BoolToVisibilityConverter x:Key="boolToVisibilityConverter" />
</ResourceDictionary>
</UserControl.Resources>
If you're looking to provide design time data for your views may I suggest reading this article. It shows how to use Blend to create design time data within your project which is not included in the release builds of the application.
Hope it helps.

Categories

Resources