Where to Load ContentControl Styles in Avalonia Class Library? - c#

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;
}

Related

Opening new window in a MVVM WPF Class Libary

Basically I am looking for this Answer, but for Class Libraries.
I am currently maintaining a larger WPF Solution where each part is loaded as a Add-In.
I'd like to create a service for all of these Add-Ins to implement which opens a Window without using UI elements in the ViewModel, basically looking like this:
class WindowService:IWindowService
{
public void ShowWindow(object viewModel)
{
var win = new Window();
win.Content = viewModel;
win.Show();
}
}
To simplify it, let's say we have 4 Projects:
1. Client
Client.exe - MainWindow and App.xaml is in here
Services.dll - Services like WindowService and Reflection are in here
Interfaces.dll - Contains Interfaces for above assemblies
2. Add-Ins
AddIn.dll - WPF Class Library, Contains WPF View/ViewModel, References to Interfaces
The Answer I linked defines DataTemplates in the App.Xaml, but I can't define DataTemplates for Classes I don't know at that point, since Add-Ins are loaded using Reflection at Runtime.
Where would I put my DataTemplates in order to get the linked Answer to work for Class Libraries without an App.Xaml?
What I do is have a xaml resource file in each of my class libraries, and programatically merge these into the main application resources.
Say the class library project assembly name is "MyProject.Client" and it contains the resource file \Views\Resources\LibraryStyles.xaml. The code to merge this would look like:
Application.Current.Resources.MergedDictionaries.Add(
new ResourceDictionary
{
Source = new Uri("pack://application:,,,/MyProject.Client;component/Views/Resources/LibraryStyles.xaml",
UriKind.RelativeOrAbsolute)
});

Using Custom Resource in DevExpress WPF Theme

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.

How to dynamically load XAML for getting Controls information

I am trying to load a Silverlight project to read every XAML file by creating an instance using reflection, Activator.CreateInstance, of every XAML class for reading its controls.
C# Code:
string strPath = "SilverlightUI.dll";
StreamResourceInfo sri = Application.GetResourceStream(new Uri(strPath, UriKind.RelativeOrAbsolute));
AssemblyPart assemblyPart = new AssemblyPart();
Assembly assembly = assemblyPart.Load(sri.Stream);
Type[] typeArray = assembly.GetExportedTypes();
foreach (Type type in typeArray)
{
object ctl = (object)Activator.CreateInstance(type);
// Following exception is occurring while creating an instance using above line of code
// Exception "Cannot find a Resource with the Name/Key ComboBoxStyle"
}
Perhaps, reflection is not able to recognize Silverlight style ComboBoxStyle. How can i possibly create an instance to read every control in the XAML file dynamically?
I have managed to find the required solution to my problem after struggling with the Google.
Copy all the Style Resources from Silverlight project (intended to load).
Paste them in the App.xaml of the Master/Caller Silverlight project or application, which is using the reflection code to load the Silverlight Controls information
Following these steps will eliminate the XAML Parse Exception of missing Style.
Cannot find a Resource with the Name/Key ComboBoxStyle
Reference: XAML Parser cannot find resource within dynamically loaded XAP when creating form instance
I was able to load custom controls using the XamlReader class.
I am using plain string that contains the XAML of the control not like your reflection idea.
//string xaml = "<...>";
var content = XamlReader.Load(xaml) as FrameworkElement;
this.scrollViewer.Content = content;
The type XamlReader is in System.Windows.Markup.
If it is possible in your case you can try to get XAML resources from your assembly and read them to string. Then use the provided code. After you have the content variable you can do anything using the Silverlight API to the control.
Hope this will help you.

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.

Loading ResourceDictionary dynamically

I have a folder in my project, Templates, full of (compiled) XAML ResourceDictionaries.
In a UserControl, I want to load all the templates into the ResourceDictionary. I would use code like the following:
public MyView()
{
InitializeComponent();
foreach (var resourceUri in new GetResourceUrisFromTemplatesFolder())
Resources.MergedDictionaries.Add(
new ResourceDictionary
{ Source = new Uri(resourceUri, UriKind.Relative) });
}
What I need to write is the GetResourceUrisFromTemplatesFolder method. I need it to discover all the resources from that folder.
The URIs could take a form like /MyAssembly;component/MyNS/Templates/Template12345.xaml or ../../Templates/Template12345.xaml
Is this possible?
Do I have to manually convert the names from the assembly's compiled resources (MyAssembly.g.resources)?
BTW, one can also manually load a ResourceDictionary as it seems:
ResourceDictionary dict = new ResourceDictionary();
System.Windows.Application.LoadComponent(dict,
new System.Uri("/SomeAssembly;component/SomeResourceDictionary.xaml",
System.UriKind.Relative));
After that, can talk to that dict object using foreach on the Keys property it has etc
At http://msdn.microsoft.com/en-us/library/ms596995(v=vs.95).aspx says
The XAML file that is loaded can be either an application definition file (App.xaml, for example) >or a page file (MainPage.xaml, for example). The XAML file can be in one of the following locations:
Included in the application package.
Embedded in the application assembly.
Embedded within a library assembly at the site of origin.
Do I have to manually convert the names from the assembly's compiled resources (MyAssembly.g.resources)?
That might be the case and i myself would approach it that way, however the question about how to do just that has been answered already so it should be not that much of an issue. Make sure that the dictionaries' compile action matches, and you probably want to prefix the names with a pack uri.

Categories

Resources