I have an application where users can define custom elements by adding their own xaml files. These files are loaded by the application on startup. The idea is to be able to extend the application without having to recompile it. These elements will be shown on screen to the user, and the xaml files may contain resource keys which are not included in the resx files of the application itself. So I have a requirement to have separate resx files which the user can edit, that will also be loaded at runtime.
I've looked at the ResourceManager class, and I know that it can be set to access resources from various other assemblies. But from what I gather the resources must be part of an assembly, which is precisely what I don't want. What I want is to have a bunch of files like these:
%ProgramData%\MyApplication\Resources\strings.resx
%ProgramData%\MyApplication\Resources\strings.de-DE.resx
%ProgramData%\MyApplication\Resources\strings.zh-CN.resx
and I want my application to be able to load these files and access the strings in them.
This article https://msdn.microsoft.com/en-us/library/gg418542(v=vs.110).aspx shows two approaches using ResXResourceReader and ResXResourceSet. However, it appears that these classes take a path to a specific file (or a stream), and therefore won't be able to pick the correct file according to current culture and the naming convention. I know I can code this myself, and that's what I'll do if I don't find a better solution. But ideally I'd want something that handles this for me. For instance if it would be possible to point a ResourceManager to a folder instead of an assembly. Is something like this possible?
after a long long searching i found this:
If i understand you in the right way you want to create user defined forms in WPF at runtime using xaml.
I am not sure but, i think the resource files will not help you out, because all xaml forms precompiled in the assembly. I found something that maybe sounds like a solution for your problem. Its called Sattalite Assembly. In the first step you have to create a new resource file. Second step is to link it with the Assembly Linker (Al.exe). form .net Framework. MSDN Creating Satellite Assemblies
First step create a ResourceFile at Runtime (here a little help)
public static void CreateResourceFile()
{
string resourceFileName = "externeresource";
System.Xml.XmlDocument xmldoc = new XmlDocument();
XmlTextReader reader = new XmlTextReader("/runtimeWPForm.xml");
reader.WhitespaceHandling = WhitespaceHandling.None;
xmldoc.Load(reader);
ResourceWriter resourceWriter = new ResourceWriter(resourceFileName);
/*Add XmlDocument must be Serializable to store it in
resourceWriter.AddResource("xamlgrid", xmldoc.ToString());
the Resource, so i stored a String here. (not testet)*/
resourceWriter.Close();
MessageBox.Show("File " + resourceFileName + " created");
reader.Close();
}
Second step create a Sattlelite Assembly from resource file with Assembly Linker Al.exe
Last step is to load the xaml Forms from the Sattalite Assembly (here a little help)
Uri GridUri = new Uri(/*Note1*/, UriKind.Relative);
Windows.Resources.StreamResourceInfo sri = Application.GetResourceStream(GridUri);
System.Windows.Markup.XamlReader xrdr = new System.Windows.Markup.XamlReader();
Grid grd = (Grid)xrdr.LoadAsync(sri.Stream);
this.Content = grd;
Note1:
Resource file - referenced assembly Pack URIs in WPF
Uri uri = new Uri("/ReferencedAssembly;component/ResourceFile.xaml",UriKind.Relative);
Here some usefull Information as i think Construct XAML Forms at Runtime with Resource Files
This is all i found for you and no guarantee for functionallity.
Im interested in if this will work for you, so please send an answer on success or if you solved your issue.
PS.: if this works i think you only have to restart you main application to load the new sattalite assembly. Maybe you have to start an ohter application that do the job and after finish automaticly start your main app again.
Best reguards
GatewayToCode
Related
I need to display a local HTML file with both images and fonts that are included via an external or internal style sheet. I want to use the new webView control introduced with .NET Framework 4.8 for WinForms.
I tried a few different ways. Hosting an HTTP Server with the font and the images do work. Sadly that's not a solution when rolling out the program as it is too much configuration for each individual pc.
Encoding the images and font to base64 strings and using them as the source does work. But I would like to use the NavigateToLocalStreamUri Method to have a solution which is easy to read and doesn't need too much overhead to make it work.
The NuGet package Microsoft.Toolkit.Forms.UI.Controls.WebView is installed in its most recent stable version 5.1.1
Below you see the code I am currently using to try and get the NavigateToLocalStreamUri running.
var uri = new Uri(someLocalPath);
webView1.NavigateToLocalStreamUri(uri, new CustomUriToStreamResolver());
private class CustomUriToStreamResolver : IUriToStreamResolver
{
public Stream UriToStream(Uri uri)
{
var stream = new FileStream(uri.AbsolutePath, FileMode.Open);
return stream;
}
}
The expected behaviour is opening the HTML file that is located at someLocalPath
Instead it throws the following exception, which I do not fully understand.
Could not find any resources appropriate for the specified culture or the
neutral culture. Make sure \"Microsoft.Toolkit.Win32.UI.Controls.DesignerUI.resources\"
was correctly embedded or linked into assembly
\"Microsoft.Toolkit.Forms.UI.Controls.WebView\" at compile time, or that all
the satellite assemblies required are loadable and fully signed.
Is the problem my CustomUriToStreamResolver or is there an underlying issue I am unaware of?
If you know of any other way to open the someLocalPath HTML file with the webView control in WinForm please do let me know.
Instead of using the current directory or getting the assembly, just use the Application.ExecutablePath property:
//using System.IO;
string applicationDirectory = Path.GetDirectoryName(Application.ExecutablePath);
string myFile = Path.Combine(applicationDirectory, "Sample.html");
webMain.Url = new Uri("file:///" + myFile);
webView.NavigateToLocal("local file") seems to work:
this.webView.NavigateToLocal(".\\file.html");
I'm not sure why Navigate doesn't work.
EDIT: Visual Studio shows NavigateToLocal as obsolete and to use NavigateToLocalStreamUri instead. See https://learn.microsoft.com/en-us/uwp/api/windows.ui.xaml.controls.webview.navigatetolocalstreamuri.
EDIT #2: I was not able to get NavigateToLocalStreamUri to work with a Uri that refers to the local file. I ended up hard coding the local file name in my implementation of IUriToStreamResolver.
I have a file I need to access at runtime, I've included it in my project and set it up as embedded resource (it's actually a source file, I changed the extension to .cs.txt to get around VS trying to compile it. That shouldn't matter, but I'm mentioning it anyway just in case).
When I try getting the file
var assembly = Assembly.GetExecutingAssembly();
Stream stream = assembly.GetManifestResourceStream(resourceName);
I get a null. I've made sure I'm using the Namespace.Folder.Filename notation, but that didn't help. It appears the file is actually not there, because when I call
assembly.GetManifestResourceNames();
I get an empty string array. Any idea what could be the case?
I appreciate this is an old thread but what I found this morning might be useful to others.
I had a resource where the filename had multiple dots in it...
example filename: data.txt.dat
var resources = asm.GetManifestResourceNames(); // None found (empty array)
renamed to data.txt (still just an embedded resource in the project configuration
var resources = asm.GetManifestResourceNames(); // Entry found ("Assembly.Namespace.data.txt")
So maybe there is some limitation around multiple . characters in the name
So I got around this by using the VS resource manager. Now I can access the file directly like this:
MyNamespace.Properties.Resources.MyFile
I'd recommend this approach to anyone, as it seems not only much cleaner, but safer as well. Thanks Hans Passant for the advice.
Based on this pull request (https://github.com/dotnet/msbuild/pull/5824) you can add WithCulture="false" in your csproj on your EmbeddedResource tag :
<EmbeddedResource Include="a.cs.b" WithCulture="false"/>
It is working for me
I've working on a project where I'm using ResourceManager extensively and this question just crossed my mind.
Can we read from .resx files without using ResourceManager? I mean, is there another way?
ResourceManager is a convenience class, it works very well with the way the build system supports .resx files. No, it is not a strict necessity.
A .NET assembly has the generic capability of embedding arbitrary data into the manifest of the assembly. Just a blob of bytes, it can be anything you want. Directly supported by the build system as well, just add a file to your project and set its Build Action to "Embedded Resource". At runtime, you retrieve the data in that file with Assembly.GetManifestResourceStream().
You can stop right there, but that's just a single file, it doesn't scale very well if you have many small resources you want to embed. Which is where a .resx file starts, it is an XML file that contains resources in a friendly format. One that gives you a fighting chance to recover the source again when the original got lost.
But an XML format is not a very good format for resource data, it is bulky and it is expensive to find data back. So .NET has resgen.exe, a build tool that turns the XML file into a binary file, a .resources file. Compact and easy to find stuff back. And fit to be embedded directly as a single manifest resource.
What you don't want to do is having to read the .resources data yourself. You'll want to use a helper class that can find specific resources back from the blob of bytes. You want use the ResourceReader class, its GetResourceData() lets you specify the resource name and it will spit the resource type and data back out.
You can stop right there, but an app often has a need for different sets of resources. A very common localization need. Which is what satellite assemblies are all about, different assemblies that contain nothing but resources, each for a specific culture. They are separate so you don't pay for the virtual memory that's required to store all the localized resources when you need only one set of them. What's needed here is a helper class that automatically locates and loads the correct satellite assembly and retrieves the resource for you, based on the current culture.
That helper class is ResourceManager.
If you choose to skip the use of the ResourceManager you can let Visual Studio handle code generation for you. Ultimately the generated code uses a ResourceManager, but you're no longer writing that code manually. Additionally, you get compile-time checking since you're referencing a generated static class.
If you add a resource file to your project and double click it from the Solution Explorer, Visual Studio presents you with a dialog where you can enter a name for a resource, and its value. The dialog presents you with options to add resources as strings, images, audio, etc. (look at the dropdowns at the top of the dialog). Next, to get the code generation bit, you need to set the Access Modifier to either "Public" or "Internal". The third option is "No code generation."
For example, add a resource file called "MyResources", then add a string resource with the name Greeting and a value of Hello! With one of the former two options selected for code generation (start off with public to test it, restrict the access as needed), you should now be able to reference the resources from your code via MyResources.Greeting. If you don't see it right away, make sure you've saved the file and try compiling.
string greeting = MyResources.Greeting; // "Hello!"
If you add other resource types (image, audio, etc.) then the return types will differ, of course.
At this point you could inspect the generated .cs file and see that the generated code is using a ResourceManager. The other use for resource files is localization. Let's say you wanted a Spanish version of MyResources. You would add a new file called MyResources.es.resx, where es corresponds to the language code desired (Spanish in this case). Now add the same resource name of Greeting with a Spanish value of Hola!.
If you change the thread culture to Spanish, referencing the resource will now return the Spanish version:
string defaultGreeting = MyResources.Greeting; // "Hello!"
Thread.CurrentThread.CurrentUICulture = CultureInfo.GetCultureInfo("es");
string spanishGreeting = MyResources.Greeting; // "Hola!"
Note that you only really need to set the access modifier to one of the code generation options for your default resource file (i.e., MyResources.resx), not for all the other localized versions you add. There's no harm in doing so, but in my opinion it's cleaner to have the main file generated while the others just have the resource values desired without code generation.
Well, Resources are compiled into the assembly. You could try to read the assembly by reading the bytes (or the IL), and extract the resources from there.
ResourceManager does this all for you, so I could not think of any reason you want to do this... Maybe one, if you don't want to load the assembly in memory, you could do it without ResourceManager.
Ref Microsoft: Represents a resource manager that provides convenient access to culture-specific resources at run time.
I expect, I'd you use multi Lang, you will get a more consistent result and better compatibility.
IMHO
I need to know the way to properly access strings and images from a resource DLL that has multiple resource files (.resx).
I have a very large project that I am working on and is composed of several winforms each with multiple panels. I am trying to tidy things up a bit by making each form has its own res file and limit the application to one resource DLL instead of multiple resource DLLs.
The problem is when I try and add a new resource file to my resource DLL and try to access a string, for example, that is being saved in the newly created resx file. ResourceManager return a null string.
Here is how I am creating my ResourceManager object and getting the string
static public string GetResourceString(string sStringName)
{
System.Reflection.Assembly myDllAssembly = System.Reflection.Assembly.LoadFile(ResPath);
string[] allResourcesinDLL = myDllAssembly.GetManifestResourceNames();
ResourceManager ResInstance = new ResourceManager(allResourcesinDLL[0].Replace(".resources", string.Empty), myDllAssembly);
return ResInstance.GetString(sStringName);
}
Where ResPath is the path to my resource DLL and sStringName is the resource/string title.
I Am not sure if there is a way to specify the resx file to read from or if it should be handled in some other way. Please bear in mind that my application is compiled using the .net 2.0 framework so I am kinda limited to the APIs that I am able to use.
I would like to load a custom file put as a resource file in my silverlight application, but the FileStream doesn't works (since I must stay in a partial trust environnment).
Is there any solution to load my file? (it is a binary serialized data).
UPDATE
Answer found :
I put my file as a "Resource" (not embedded neither content or anything else)
Loaded it like this :
StreamResourceInfo info = Application.GetResourceStream(new Uri(#"/Utilitaires;component/Resources/" + name, UriKind.Relative));
And then using the "info.Stream" property.
Now, I have an other asking. By doing like this, the file is added to the assembly (to the exe/dll), and make it a bit bigger.
But since these datas need to be loaded at the same time as the assembly, should I let them as a resource, or use another method to load them separatly? (and what should be the method? I need it to work in local as well as on a server).
Thanks,
KiTe
Since you need the resource at the same time as you load the assembly then the only other reason to place the file outside the Xap would be to allow the file to be modified without modifying the Xap.
Personally I would include the file as "Content" rather than "Resource". This means that the file ends up as an entry in the Xap (which is just a Zip file) rather than inside a dll.
You still use GetResourceStream to access it but the Url becomes something like:-
new Uri(#"/Assets/" + name, UriKind.Relative)
Where Assets is a folder you create in your project to store additional files, also name should include a file extension.
Using this approach kind of gives you the best of both worlds. The file is included in the Xap but if for some reason the file content needs to be modified the Xap can be opened as a Zip file and the file replaced.