I've added a XAML file to a Windows Phone 8 project. Its build action is "Page". I want to load the XAML as a text string (to feed into XamlReader.Load()). How can I accomplish this?
It's not available as a separate file in the XAP package; it's probably somewhere in the DLL.
When set to Page, the compiler will compile the XAML into BAML and add the BAML file as a resource to the assembly.
If you wish to get the original XAML back out from the BAML resource at runtime, then you will need to deserialize the BAML, and then serialize your object to XAML.
You can have a look at the Baml2006Reader, or a better option would be to use Application.LoadComponent which is what the InitializeComponent method uses internally. InitializeComponent is called by the partially generated class for your XAML code behind.
var uri = new Uri("/MyAppName;component/MyXaml.xaml", //Note extension: XAML, not BAML
UriKind.Relative);
Page rootObject = new Page(); //Assuming XAML root element is Page - it could be anything
Application.LoadComponent(rootObject, uri);
(assuming the root element of your XAML file is a Page).
You can then serialize the Page to a XAML string using the XamlWriter:
string xaml = XamlWriter.Save(rootObject);
Note that this is the XamlWriter in the System.Windows.Markup namespace, not System.Xaml. If your XAML has WPF types, then you should use this XamlWriter to avoid errors.
Related
I'm building an application using PRISM and MVVM. I have a view model that needs to display a non-modal dialog box to the user indicating an operation is in progress. I'm using essentially an abstracted IDialogService.
My question is: where should I store the strings for the title and the message shown in this dialog box? The view model's logic causes the dialog box to be displayed and determines when it should be closed. Hence, I have code that looks like this in my view model:
let! closeDlgAction =
dialogSvc.ShowDialogModeless (
"Opening File",
"Please wait while your selected file is opened.") |> Async.AwaitTask
I'm thinking about localization scenarios. WPF has its own mechanism for providing localization through resource dictionary, etc. It seems like these strings belong in a resource dictionary, but the view model shouldn't have a dependency on WPF resource directories - especially because the same view model is going to be used on a Xamarin Forms application later.
The best solution that comes to mind is to use a service that abstracts the resource library away (e.g. IDialogStringService), but I wonder if there's a better or more preferred approach?
You shouldn't use resource dictionaries (xaml) to store text. Instead you have to use Resources (*.resx). In VS:
Right click on project
Add -> New Item...
Find "Resources File" template, type name, and click Add
Opt. Open this file (special editor will opened) and on top bar switch Access Modifier to Public, if you want get access to text from another project or from XAML. Add some key\value strings.
Right click on resource file and click Run Custom Tool. New class will generated with static properties with names based on your keys from Step 4.
How to use (if file has name Localizations.resx and has string with key "AppTitle")
From code:
let! closeDlgAction =
dialogSvc.ShowDialogModeless (
Localizations.AppTitle,
"Please wait while your selected file is opened.") |> Async.AwaitTask
From xaml:
<Window
x:Class="MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="{x:Static Localizations.AppTitle}"/>
*.resx file and *.cs file that is generated both don't depend on any WPF assemblies, so you can use them in different assemblies: in shared view models, from wpf views and from xamarin views. Just put you *.resx file in separate netstandard assembly and refer to it where do you need it from
Cons of this way:
resx generates class with strings and each string is public property, so static code analyze works
You don't have add new abstraction level
You can ref strings from code files or from XAML
I liked Vadim's answer, and I have used that approach before. If my View Models lived in the same project as the WPF project, that would be the best solution.
However, my View Models are in a different library (and a different language) and will be shared between a Prism MVVM WPF project and a Prism MVVM Xamarin Forms project. I could still use resources in the View Model library, but then localization concerns would exist separately in both the WPF project (for the Views) and the View Model library. IMO the localization concern should be centralized.
As such, I decided to abstract the resources behind a service. Implementing the resource service turned out to be more straightforward than I thought. To use an indexer intuitively, I defined a "resource container object" that is returned by IResourceService, as seen below:
public struct ResourceContainer
{
private readonly Func<string, string> _resourceGetter;
public string this[string resourceId] => _resourceGetter(resourceId);
public ResourceContainer(Func<string, string> resourceGetter) => _resourceGetter = resourceGetter;
}
public interface IResourceService
{
ResourceContainer Resources { get; }
}
And the service implementation in the WPF library is as follows:
public class ResourceService : IResourceService
{
public ResourceService()
{
Resources = new ResourceContainer((s) => Application.Current.Resources[s] as string);
}
public ResourceContainer Resources { get; }
}
In the WPF layer's XAML resource directory:
<s:String x:Key="FileOpenDialogTitle">Opening File</s:String>
<s:String x:Key="FileOpenDialogMessage">Please wait while your selected file is opened.</s:String>
And, finally, the View Model consumes this service by requesting IResourceService on its constructor, and is used as follows:
let! closeDlgAction =
dialogSvc.ShowDialogModeless (
resourceSvc.Resources.["FileOpenDialogTitle"],
resourceSvc.Resources.["FileOpenDialogMessage"]) |> Async.AwaitTask
This approach will ultimately require implementing the resources twice - once for the WPF project and once for the XF project, but I have to implement the Views twice, anyway. At least the localization concerns are centralized in both cases (or perhaps a shared resource library can be used between both projects).
EDIT: This technique could also leverage Vadim's suggestion by putting the localization resource (.resx) in the WPF project as well, and either having the XAML resource directory reference the static resources, or have the ResourceService return the resource directly. Having the resources in .resx format may make sharing them between multiple projects more straightforward.
Everything happens within the same VS project. I have a resource dictionary file living on it's own. When I try to load it programmatically I get the error
"Cannot create unknown type '{clr-namespace:MyAssembly.Helpers}IsNullConverter".
Here is how I load it :
StreamResourceInfo stream = Application.GetResourceStream(new Uri(#"MyAssembly;component/Resources/Resources.xaml", UriKind.Relative));
this.dynamicResources = XamlReader.Load(stream.Stream) as ResourceDictionary;
And here is the resource dictionary :
<ResourceDictionary 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:helpers="clr-namespace:MyAssembly.Helpers">
<helpers:IsNullConverter x:Key="IsNullConverter" />
Styles go here...
Note that it is tied to a code-behind file, but there is nothing in it. The Build-Action of the resource file is set to "Resource". This is driving me crazy since this morning and still no clue what the heck is going on...
Help.
Thank you.
Halelujah I fugured it out. All I had to do is load the resource dictionary directly
Uri uri = new Uri(#MyAssembly;component/Resources/Resources.xaml", UriKind.Relative);
this.dynamicResources.Source = uri;
And make sure Build Action of resource dictionary file is set to "Page"
\m/
Is the assembly referenced by your project? If not try adding a reference - if you don't want a dependency you could try loading the assembly:
http://www.dreamincode.net/forums/topic/78974-using-reflection-to-load-unreferenced-assemblies-at-runtime/
Alternatively you could add a x:Class definition to the resourcedictionary, and instantiate the class from the assembly instead of loading the xaml, and remember to call the generated InitializeComponent() from the constructor, the it will load.
Is it possible to set code behind a resource dictionary in WPF for event handling?
Your example would work fine if the ResourceDictionary and converter was in the same assembly as where you load from, as far as I can see :)
I have been integrating image resources to my WPF assemblies for quite a time, but I never really found a really pretty way of binding them in my MVVM applications.
So I was wondering if some you, stackoverflow users, could share their own practice when it comes to assembly ressources:
How do you reference them in C#/VB and XAML?
When exposing them from code, what type do you expose? (BitmapImage, Uri, string, ...)
Do you prefer referencing them from the view or binding them through view-model?
Do you use a specific assembly to store them? How do you organize them?
Thanks for your help ;-)
This is what I do...
Images are kept in some folder such as Images in project's root folder. You can keep them in any assembly.
Individual image has Build Action property set to Resource type.
The ViewModel holds image name in string (say property MyImageName) and I convert it as a relative path when bound to the Source of Image object in XAML...
public class ImagePathConverter
{
public void Convert(...)
{
return "pack://application:,,,/MyApplicationName;component/Images/" + value.ToString();
}
}
where MyApplicationName is the short assembly's name that is having the Images folder under it.
In XAML, assuming the view model instance is bound to the data context of the entire view and / or atleast to the image's data contect ....
<Image Source="{Binding Path=MyImageName, Converter={StaticResource ImagePathConverter}}" />
But then thats one of the typical ways to refer images in a WPF project via MVVM. Other ways include an Image's binary stream loaded (from resx or Content type images or from binary database). In such case your converter will convert it into a BitMapImageSource.
Wondering how to accomplish setting the Style xaml with the code in F#. The code is simple enough:
this.DefaultStyleKey <- typeof<MyControl>
In a C# project the build options allow you to mark the XAML as a resource custom build command of: MSBuild:Compile
I don't see it in the properties panel, so I tried to add it by hand to the project file myself...
Any ideas? The application loads - the custom control has no output (but the code executes).
Thanks
UPDATE:
I checked the manifests and the resource was included as expected between my project and the project I am porting... Looking for a next step.
UPDATE 2:
Well it may be included in the manifest OK - but it is not being "compiled" as the C# version of the project throws an error in the build process when I malform the XML while the F# version allows the malformed XML to be brought into the application.
UPDATE 3:
Loading the XAML is fine now (i guess) however I am having some issues with the properties of the control:
static member ItemsProperty : DependencyProperty =
DependencyProperty.Register(
"Items",
typeof<MyMenuItemCollection>,
typeof<MyMenu>,
null);
member this.Items
with get () : MyMenuItemCollection = this.GetValue(MyMenu.ItemsProperty) :?> MyMenuItemCollection
and set (value: MyMenuItemCollection) = this.SetValue(MyMenu.ItemsProperty, value);
The problem occurs on access:
for menuItem in this.Items do
let contentElement: FrameworkElement = menuItem.Content
where I get a null pointer exception on this.Items; however I have it initialized in the constructor:
do
this.Items <- new CoolMenuItemCollection()
The C# style of compilation of XAML files is not supported by the F# tools for Visual Studio, so there is no way to get the same behavior as in C#. I think you have two options:
Create a C# project with XAML files and reference F# library which implements the core functionality (or reference C# library from F# and load user interface from the C# library in your F# application)
Use XamlReader object (see MSDN) and load the XAML file (embedded in resources in the simple way) programmatically. You won't get any of the C#-compiler generated features (e.g. named properties for all objects with x:Name), but otherwise, it should work in the usual way.
Normally, all resources are put in app.xaml, or other resources xaml files (as resource dictionary) and then reference it in app.xaml.
When applying prism pattern, for those module, there is no app.xaml file. Application class is replaced by a class implementing interface IModule. So where is the right place for resources used by controls in the module?
this is how i do it: have modules register resources with the app.
Composite WPF (Prism) module resource data templates
You can add the resource dictionary in the same assembly as the module or in other loaded assembly, then access it programmatically by using the Application.GetResourceStream method; however you will need to know the name of the assembly where the resource is defined.
For example, if your assembly is named TheAssembly and the resource dictionary is named TheDictionary.xaml, you can do (Disposes not shown for brevity):
StreamResourceInfo sr = Application.GetResourceStream(
new Uri("/TheAssembly;component/TheDictionary.xaml", UriKind.Relative));
StreamReader r=new StreamReader(sr.Stream);
string xaml=r.ReadToEnd();
ResourceDictionary rd = (ResourceDictionary)XamlReader.Load(xaml);
From here, you can for example use the Unity container to make the resource dictionary available application-wide.
UPDATE
The following version avoids having to hard-code the assembly name, provided that the resource is in the currently executing assembly:
string assemblyName = Assembly.GetExecutingAssembly().FullName.Split(',')[0];
string uri = string.Format("/{0};component/Dictionary1.xaml", assemblyName);
StreamResourceInfo sr = Application.GetResourceStream(
new Uri(uri, UriKind.Relative));
//etc...