Load xaml viewmodel at run-time with windows phone - c#

I'm currently following a windows phone tutorial from Microsoft virtual academy and one of the challenges was to use the design xaml viewmodel that was created in the project and loaded at run-time.
After researching this for hours, I thought it was time to resort to stackoverflow as I'm not getting anywhere. I've read numerous articles and none are giving me a correct answer, so I've got a few questions:
How to fix my error?
How to load the xaml model view at run-time programmatically?
How to load the xaml model view at run-time using xaml?
Where to call the loading of the xaml at run-time
The sample data file i.e. SoundViewModelSampleData.xaml, looks like this:
<vm:SoundViewModel
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="clr-namespace:Soundboard.ViewModels"
xmlns:mo="clr-namespace:Soundboard.Models">
<vm:SoundViewModel.Animals>
<vm:SoundGroupViewModel Title="Animals Sample">
<vm:SoundGroupViewModel.Items>
<mo:SoundDataModel Title="Animals 1" FilePath="Animals.wav" />
</vm:SoundGroupViewModel.Items>
</vm:SoundGroupViewModel>
</vm:SoundViewModel.Animals>
<vm:SoundViewModel.Cartoons>
<vm:SoundGroupViewModel Title="Cartoons Sample">
<vm:SoundGroupViewModel.Items>
<mo:SoundDataModel Title="Cartoons 1" FilePath="Cartoons.wav" />
<mo:SoundDataModel Title="Cartoons 2" FilePath="Cartoons.wav" />
</vm:SoundGroupViewModel.Items>
</vm:SoundGroupViewModel>
</vm:SoundViewModel.Cartoons>
</vm:SoundViewModel>
The simplest code to load this programmatically that I found was:
string path = #".\SampleData\SoundViewModelSampleData.xaml";
using (System.IO.StreamReader reader = new System.IO.StreamReader(path))
{
SoundViewModel vm = XamlReader.Load(reader.ReadToEnd()) as SoundViewModel;
}
While I'm probably calling it from the wrong location for now, I'm getting the following error:
A first chance exception of type
'System.Windows.Markup.XamlParseException' occurred in
System.Windows.ni.dll
{System.Windows.Markup.XamlParseException: Unknown parser error:
Scanner 2147500037. [Line: 5 Position: 14] at
MS.Internal.XcpImports.CreateFromXaml(String xamlString, Boolean
createNamescope, Boolean requireDefaultNamespace, Boolean
allowEventHandlers, Boolean expandTemplatesDuringParse, Boolean
trimDeclaredEncoding) at
System.Windows.Markup.XamlReader.Load(String xaml) at
Soundboard.ViewModels.SoundViewModel.LoadData()}
Unknown parser error: Scanner 2147500037. [Line: 5 Position: 14]
Assuming I can resolve this error, this would take care of my question 1 & 2 (fixing error and loading the data programmatically)
Can you spot what's causing this problem?
As mentioned above, I'm probably loading this in the wrong place i.e. from within my ViewModel when it is created when the app loads.
namespace Soundboard.ViewModels
{
public class SoundViewModel
{
public SoundGroupViewModel Animals { get; set; }
public SoundGroupViewModel Cartoons { get; set; }
public bool IsDataLoaded { get; set; }
public void LoadData()
{
string path = #".\SampleData\SoundViewModelSampleData.xaml";
using (System.IO.StreamReader reader = new System.IO.StreamReader(path))
{
SoundViewModel vm = System.Windows.Markup.XamlReader.Load(reader.ReadToEnd()) as SoundViewModel;
}
IsDataLoaded = true;
}
}
}
And in my app.xaml.cs I have the following:
public static SoundViewModel SoundViewModel
{
get
{
if (_soundViewModel == null)
{
_soundViewModel = new SoundViewModel();
_soundViewModel.LoadData();
}
return _soundViewModel;
}
}
Now how can I achieve the same using just xaml for the run-time and use d:datacontext for design-time.
I've read a few articles but they are all for wpf but most are related to loading usercontrol, etc.. but not a viewmodel
Any help would be greatly appreciated.
Thanks.

I was busy on a similar problem as yours with XamlReader. I found that you should define the assembly namespace in the root element of your xaml file even if it is included in the same assembly. In the example code below even if the xaml is included in SoundBoard.dll, I declare its namespace in the xaml file.
xmlns:vm = "clr-namespace:SoundBoard.ViewModels;assembly=SoundBoard">

Also tried doing this and the best I could come up with was to move the data XAML file to assets, mark it as a Resource (I removed the Custom Tool as well), and then load it with the following:
public void LoadData()
{
// Load data
//LoadCodeData();
LoadXamlData();
IsDataLoaded = true;
}
private void LoadXamlData()
{
string path = "SoundBoard;component/assets/runtimecontent/SampleData.xaml";
Uri uri = new Uri(path, UriKind.Relative);
using (System.IO.StreamReader reader = new System.IO.StreamReader((System.Windows.Application.GetResourceStream(uri)).Stream))
{
SoundModel model = System.Windows.Markup.XamlReader.Load(reader.ReadToEnd()) as SoundModel;
this.Animals = model.Animals;
this.Cartoons = model.Cartoons;
this.Taunts = model.Taunts;
this.Warnings = model.Warnings;
this.CustomSounds = model.CustomSounds;
}
}
I also did what Bahti suggested.

Related

Resources in modularized WPF (with Caliburn.Micro and MEF)

I have searched for an answer for this question all day without coming up with any solutions directly applicable to my case, or anything that works (in the one case I found that was applicable).
I have a Caliburn.Micro framework set up to use MEF, and I load my modularized elements just fine. The one thing missing is getting WPF to recognize the resources I use in one of my modules.
How modules are loaded in my app bootstrapper
[ImportMany]
private IEnumerable<IMyModule> _myModules;
protected override void Configure()
{
// Because Configure() is also called from SelectAssemblies(), we cannot instantiate MEF again because it will create conflicts.
if (_configured)
{
return;
}
AggregateCatalog aggregateCatalog = new AggregateCatalog(AssemblySource.Instance.Select(x => new AssemblyCatalog(x)).OfType<ComposablePartCatalog>());
aggregateCatalog.Catalogs.Add(new DirectoryCatalog(ConfigurationManager.AppSettings["MyModuleFolderLocation"]));
aggregateCatalog.Catalogs.Add(new AssemblyCatalog(GetType().Assembly));
_container = new CompositionContainer(aggregateCatalog);
CompositionBatch batch = new CompositionBatch();
batch.AddExportedValue<IWindowManager>(new WindowManager());
batch.AddExportedValue<IEventAggregator>(new EventAggregator());
batch.AddExportedValue(_container);
_container.Compose(batch);
_container.SatisfyImportsOnce(this);
_configured = true;
}
protected override IEnumerable<Assembly> SelectAssemblies()
{
// SelectAssemblies() is called before Configure(), so manually force Configure() to run first so that MEF is instantiated properly
Configure();
if (!_configured)
{
throw new Exception("Unable to configure assemblies");
}
List<Assembly> assemblies = new List<Assembly>();
assemblies.Add(Assembly.GetExecutingAssembly());
// Need to add all module assemblies so that Caliburn will be able to find the View for a ViewModel
foreach(IMyModule myModule in _myModules)
{
Assembly assembly = myModule.GetType().Assembly;
assemblies.Add(assembly);
}
return assemblies.Distinct();
}
This works just fine to get a module to be displayed properly.
But when a module has used an image, this image is never displayed, because this kind of loading apparently doesn't take resources into account.
I create a Resources.resx file in the module project and add an image to that. The image file that is presented in Visual Studio then has a Build Action that says "Resource" and "Do not copy (to output directory)". This should mean that the image is embedded in the resulting DLL file.
The image is placed in a folder called "Resources" in the module project, and the XAML use it like this:
<Image Source="/Resources/myImage.png" />
The image is displayed in the preview in Visual Studio, but is not displayed when the application runs.
What I have tried that didn't work
Referencing the image in another way: <Image Source="pack://application:,,,/Resources/myImage.png" />
Getting the resources in BAML form and reinserting them into the executing assembly, like in this question: Instantiate ResourceDictionary xaml from other Assembly (which causes an OutOfMemoryException on this line var reader = new Baml2006Reader(stream);)
A lot of other answers that reference ResourceDictionary, but I have a Resource.resx file (which only generates an internal class that is not a ResourceDictionary)
The question remains
How can I get WPF/Caliburn.Micro to recognize resources from a DLL loaded by MEF?
Answer
Use this syntax for the Source property for images with Build Action: Resource
<Image Source="/AssemblyName;component/Resources/MyImage.png" />
Where AssemblyName is the name of the assembly (as defined in the project properties), and /Resource/MyImage.png is the path to the image (as defined in the project). component must always be present.
Side note
After a lot of help from #StepUp I initially decided to ask a new question using what was learned from this question and rephrasing everything to be more specific to my problem.
When writing this new question I ended up googling for phrases and commands that might help with the rephrasing, and I stumbled upon this page: http://www.geekchamp.com/tips/wp7-working-with-images-content-vs-resource-build-action
Apparently, the WPF Image control has a ton of ways to define the Source property. I had already tried quite a lot of various Source inputs and thought I had tried them all, but the page linked to above proved me wrong.
As far as I have been able to test, the syntax described above seems to work for images marked with Build Action: Resource. Therefore I no longer need to have a RESX file for the images, and I do not need any special handling when bootstrapping MEF.
At first, you should read the assembly with Style. Then, it is neccessary to read BAML files from external library using Baml2006Reader. Let me show an example:
private GetResourceDictionary()
{
string address = #"WpfCustomControlLibrary1.dll";
Assembly skinAssembly = Assembly.LoadFrom(address);
string[] resourceDictionaries = skinAssembly.GetManifestResourceNames();
Stream bamlStream = null;
string name = "themes/AllStylesDictionary.baml";//themes/AllStylesDictionary.baml
foreach (string resourceName in resourceDictionaries)
{
ManifestResourceInfo info = skinAssembly.GetManifestResourceInfo(resourceName);
if (info.ResourceLocation != ResourceLocation.ContainedInAnotherAssembly)
{
Stream resourceStream = skinAssembly.GetManifestResourceStream(resourceName);
using (ResourceReader reader = new ResourceReader(resourceStream))
{
foreach (DictionaryEntry entry in reader)
{
if (entry.Key.ToString().Equals(name.ToLower()))
{
bamlStream = entry.Value as Stream;
}
}
}
}
}
ResourceDictionary rd = LoadBaml<ResourceDictionary>(bamlStream);
Application.Current.Resources.MergedDictionaries.Add(rd);
Style style = Application.Current.Resources.MergedDictionaries[0]["myStyle"] as Style;
button.Style = style;
}
and:
public static T LoadBaml<T>(Stream stream)
{
var reader = new Baml2006Reader(stream);
var writer = new XamlObjectWriter(reader.SchemaContext);
while (reader.Read())
writer.WriteNode(reader);
return (T)writer.Result;
}
Update:
If you want to load an image from another libary, you should use the following code:
yourImage.Source = new Bitmap(System.Reflection.Assembly.GetEntryAssembly().
GetManifestResourceStream("MyProject.Resources.myimage.png"));
Update1:
To load an image from the external dll.
foreach (DictionaryEntry entry in reader)
{
if (entry.Key.ToString().Equals(name.ToLower()))
{
bamlStream = entry.Value as Stream;
BitmapImage bmp = LoadImage(bamlStream);
img.Source = bmp;
}
}
public static BitmapImage LoadImage(Stream stream)
{
BitmapImage bmi;
using (MemoryStream ms1 = new MemoryStream())
{
stream.CopyTo(ms1);
bmi = new BitmapImage();
bmi.BeginInit();
bmi.StreamSource = new MemoryStream(ms1.ToArray());
bmi.EndInit();
}
return bmi;
}

Serialization issue on custom type

I have following classes:
[Serializable]
public class TradeBotSettings
{
public ExchangePlatform Exchange
{
get;
set;
}
}
[Serializable]
public enum ExchangePlatform
{
[XmlEnum("BTC_E")]
BTC_E,
[XmlEnum("BitStamp")]
BitStamp,
[XmlEnum("CampBX")]
CampBX,
[XmlEnum("Cryptsy")]
Cryptsy,
[XmlEnum("BTCChina")]
BTCChina,
}
When i try to serialize gives error
private void Button2_Click(object sender, EventArgs e)
{
TradeBotSettings tbSettings = new TradeBotSettings();
tbSettings.Exchange = ExchangePlatform.BTC_E;
StreamWriter sw = new StreamWriter(#"D:\Temp\Trader\Trader\Trader\bin\x86\Debug\configs\bots.xml", false);
xmlSerializerTradebot = new XmlSerializer(tbSettings.GetType());
xmlSerializerSettings.Serialize(sw, tbSettings);
sw.Close();
}
Error is : An unhandled exception of type 'System.InvalidOperationException' occurred in System.Xml.dll
Additional information: There was an error generating the XML document.
This looks like a generic error anyone have a clue about this
Thanks in advance
The code you presented wont compile, xmlSerializerSettings is unknown. This probably should have been "xmlSerializerTradebot.Serialize(..." instead, and this works fine. Maybe that´s your problem?
BTW: You should use the "using" clause when creating StreamWriter instances to prevent having the file not immediately closed in case of serialization exceptions. You also dont need to use the XMLEnum attribute unless you want to have the default serialization behaviour for enums changed...
BTW2: Yeah, i know, this is not a clear answer to the problem and i should rather comment. I would if i already could... ;-)

How to get error info from dynamic C# code compiling

I have a C# application which uses a C# script interface. That means that my application will compile C# code and run it.
I am using the System.CodeDom.Compiler class to do it with.
The problem is that if I run the code below it throws an InvalidCastException because it is trying to cast a string to an int in my dynamic code.
If I catch the exception I have no indication where in the 'dynamic code' that error occured. For instance 'InvalidCastException on line 8'.
I get a stack trace, but no line numbers.
Any ideas? I want to present to our users enough information to know where their error is.
public class NotDynamicClass
{
public object GetValue()
{
return "value";
}
}
class Program
{
public static void Main()
{
var provider = CSharpCodeProvider.CreateProvider("c#");
var options = new CompilerParameters();
options.ReferencedAssemblies.Add("DynamicCodingTest.exe");
var results = provider.CompileAssemblyFromSource(options, new[]
{
#"
using DynamicCodingTest;
public class DynamicClass
{
public static void Main()
{
NotDynamicClass #class = new NotDynamicClass();
int value = (int)#class.GetValue();
}
}"
});
var t = results.CompiledAssembly.GetType("DynamicClass");
t.GetMethod("Main").Invoke(null, null);
}
}
You need to set IncludDebugInformation to true on your CompilerParameters.
Update: At the bottom of the MSDN documentation there is a community remark:
For C#, if you set this property to true you need to also set GenerateInMemory to false and set the value of OutputAssembly to a valid file name. This will generate an assembly and a .pdb file on disk and give you file and line number information in any stacktraces thrown from your compiled code.

Application.ProductName equivalent in WPF?

I have a class library that is nested two+ layers under a main GUI application, within that nested class library I want to be able to access the main applications name.
Under .Net 3.5 you could call Application.ProductName to retrieve the value from the Assembly.cs file, but I cannot identify an equivalent in WPF. If I use reflection and GetExecutingAssembly then it returns the class libraries details?
Thanks
You can use Assembly.GetEntryAssembly() to get the EXE assembly, and can then use Reflection to get the AssemblyProductAttribute from that.
This assumes that the product name has been set on the EXE assembly. The WinForms Application.ProductName property actually looked in the assembly containing the main form, so it works even if the GUI is built in a DLL. To replicate this in WPF you would use Application.Current.MainWindow.GetType().Assembly (and again use Reflection to get the attribute).
Here is another solution that I am using to get the Product Name
Public Shared Function ProductName() As String
If Windows.Application.ResourceAssembly Is Nothing Then
Return Nothing
End If
Return Windows.Application.ResourceAssembly.GetName().Name
End Sub
in wpf there are many way to do this ,
here you can find two of this.
using System;`
using System.Windows;
String applicationName = String.Empty;
//one way
applicationName = AppDomain.CurrentDomain.FriendlyName.Split('.')[0];
//other way
applicationName = Application.ResourceAssembly.GetName().Name;
If you need to get the descriptive product name as I did, then this solution may be useful:
// Get the Product Name from the Assembly information
string productName = String.Empty;
var list = Application.Current.MainWindow.GetType().Assembly.GetCustomAttributes(typeof(AssemblyProductAttribute), true);
if (list != null)
{
if (list.Length > 0)
{
productName = (list[0] as AssemblyProductAttribute).Product;
}
}
It returns whatever you've set for the 'AssemblyProduct' attribute in the AssemblyInfo.cs file, e.g. something like "Widget Engine Professional".
Based on the answers above, this works just great immediately:
var productName = Assembly.GetEntryAssembly()
.GetCustomAttributes(typeof(AssemblyProductAttribute))
.OfType<AssemblyProductAttribute>()
.FirstOrDefault().Product;
If you are looking for the values provided by the assembly information, e.g. the title...
... then you have to get the custom attributes like this:
using System.Linq;
using System.Reflection;
using System.Windows;
namespace WpfApp1
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
Title = (Assembly.GetEntryAssembly().GetCustomAttributes(typeof(AssemblyTitleAttribute)).SingleOrDefault() as AssemblyTitleAttribute)?.Title;
}
}
}
The answer you require is:
Path.GetFileName(Assembly.GetEntryAssembly().GetName().Name)

Compiling an ASPX page into a standalone program

As I mentioned here, I'm trying to generate HTML from an ASPX page inside a WinForms.
I'm trying to compile the ASPX page directly into the EXE; I'd like to be able to write something like this:
var page = new ASP.MyPageName();
var stringWriter = new StringWriter();
using(var htmlWriter = new HtmlTextWriter(stringWriter))
page.RenderControl(htmlWriter);
I added an ASPX page, set the Build Action to Compile, and put in the following Page declaration:
<%# Page Language="C#" ClassName="MyPageName" %>
The code compiles, and the properties that I defined in the ASPX are usable from the calling code, but the StringWriter remains empty. I tried calling htmlWriter.Flush, and it didn't help.
The page instance's Controls collection is empty, and it probably shouldn't be.
I looked at the EXE in Reflector and I couldn't find the page content anywhere. I therefore assume that the page isn't being compiled properly.
What is the correct way to do this?
I ended up using ApplicationHost.CreateApplicationHost to run the entire application in the ASP.Net AppDomain. This is far simpler and more reliable than my attempt to fake the ASP.Net AppDomain.
Note: In order to do this, you must put a copy of your EXE file (or whatever assembly contains the type passed to CreateApplicationHost) in your ASP.Net folder's Bin directory. This can be done in a post-build step. You can then handle AssemblyResolve to locate other assemblies in the original directory.
Alternatively, you can place the program itself and all DLLs in the ASP.Net's Bin directory.
NOTE: WinForms' Settings feature will not work in an ASP.Net AppDomain.
I believe what you want to use is the SimpleWorkerRequest.
Unfortunately, however, it requires that the resource (I believe) live on disk. From your description it sounds like you prefered for the whole app to reside in your DLL. If that is the case you will most likely need to implement your own HttpWorkerRequest.
Warning
This does not work reliably, and I've given up on it.
I ended up copying the files to the output folder and initializing ASP.Net in same AppDomain, using the following code: (I tested it; it sometimes works)
static class PageBuilder {
public static readonly string PageDirectory = Path.Combine(Path.GetDirectoryName(typeof(PageBuilder).Assembly.Location), "EmailPages");
static bool inited;
public static void InitDomain() {
if (inited) return;
var domain = AppDomain.CurrentDomain;
domain.SetData(".appDomain", "*");
domain.SetData(".appPath", PageDirectory);
domain.SetData(".appVPath", "/");
domain.SetData(".domainId", "MyProduct Domain");
domain.SetData(".appId", "MyProduct App");
domain.SetData(".hostingVirtualPath", "/");
var hostEnv = new HostingEnvironment();//The ctor registers the instance
//Ordinarily, the following method is called from app manager right after app domain (and hosting env) is created
//Since CreateAppDomainWithHostingEnvironment is never called here, I need to call Initialize myself.
//Here is the signaature of the method.
//internal void Initialize(ApplicationManager appManager, IApplicationHost appHost, IConfigMapPathFactory configMapPathFactory, HostingEnvironmentParameters hostingParameters) {
var cmp = Activator.CreateInstance(typeof(HttpRuntime).Assembly.GetType("System.Web.Hosting.SimpleConfigMapPathFactory"));
typeof(HostingEnvironment).GetMethod("Initialize", BindingFlags.NonPublic | BindingFlags.Instance).Invoke(hostEnv, new[] { ApplicationManager.GetApplicationManager(), null, cmp, null });
//This must be done after initializing the HostingEnvironment or it will initialize the config system.
SetDefaultCompilerVersion("v3.5");
inited = true;
}
static void SetDefaultCompilerVersion(string version) {
var info = CodeDomProvider.GetCompilerInfo("c#");
var options = (IDictionary<string, string>)typeof(CompilerInfo).GetProperty("ProviderOptions", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(info, null);
options["CompilerVersion"] = version;
}
public static TPage CreatePage<TPage>(string virtualPath) where TPage : Page {
return BuildManager.CreateInstanceFromVirtualPath(virtualPath, typeof(TPage)) as TPage;
}
//In a base class that inherits Page:
internal string RenderPage() {
var request = new SimpleWorkerRequest("", null, null);
ProcessRequest(new HttpContext(request));
using (var writer = new StringWriter(CultureInfo.InvariantCulture)) {
using (var htmlWriter = new HtmlTextWriter(writer))
RenderControl(htmlWriter);
return writer.ToString();
}
}
InitDomain must be called right when the program starts; otherwise, it throws an exception about the configuration system being already initialized.
Without the call to ProcessRequest, the page's Controls collection is empty.
UPDATE: The page is rendered during the call to ProcessRequest, so that must be done after manipulating the Page instance.
This code will not work if the program has a .config file; I made a method to set the default C# compiler version without a .config file using reflection.
Why dont you just look at hosting the ASP.NET runtime in your app?
There are several snippets online to show you how.
Here is one.
Most likely you are using wrong page class. You need to use not the actual nice-named class in code behind. During compilation ASP.NET generates page class, which inherits from class defined in code behind and within this class happens initialization of all the controls. Therefore you should use generated class (check its name using Reflector).
If you're looking for the MVC version of this answer, see:
Is there a way to process an MVC view (aspx file) from a non-web application?
The code uses a separate AppDomain, but as far as I could tell, this is required as all the code generated from an ASPX file depends on HttpContext and HostingEnvironment.VirtualPathProvider.
public class AspHost : MarshalByRefObject
{
public string _VirtualDir;
public string _PhysicalDir;
public string AspxToString(string aspx)
{
StringBuilder sb = new StringBuilder();
using (StringWriter sw = new StringWriter(sb))
{
using (HtmlTextWriter tw = new HtmlTextWriter(sw))
{
var workerRequest = new SimpleWorkerRequest(aspx, "", tw);
HttpContext.Current = new HttpContext(workerRequest);
object view = BuildManager.CreateInstanceFromVirtualPath(aspx, typeof(object));
Page viewPage = view as Page;
if (viewPage == null)
{
UserControl viewUserControl = view as UserControl;
if (viewUserControl != null)
{
viewPage = new Page();
viewPage.Controls.Add(viewUserControl);
}
}
if (viewPage != null)
{
HttpContext.Current.Server.Execute(viewPage, tw, true);
return sb.ToString();
}
throw new InvalidOperationException();
}
}
}
public static AspHost SetupFakeHttpContext(string physicalDir, string virtualDir)
{
return (AspHost)ApplicationHost.CreateApplicationHost(
typeof(AspHost), virtualDir, physicalDir);
}
}
Then, to render a file:
var host = AspHost.SetupFakeHttpContext("Path/To/Your/AspNetApplication", "/");
String rendered = host.AspxToString("~/Views/MyView.aspx");
you can use the ClienBuildManager class to compile ASPX files.

Categories

Resources