I'm working on the navigation part of a desktop application, and have a bit of a problem. The request is that the navigation should be dynamic, so that you can, for instance, switch orders of the views without having to recompile (and ideally also adding a view without recompiling).
Currently I'm using an XML to define which windows to display, which header it should have and how the footer should look like. Here's how the XML looks now:
<?xml version="1.0" encoding="utf-8" ?>
<ArrayOfViewState xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<ViewState ViewName="WelcomeView" Header="Welcome to the Application" FooterButton1="Quit" FooterButton2="Back" FooterButton3="Next" />
<ViewState ViewName="LicenseView" Header="Licence Agreement" FooterButton1="Quit" FooterButton2="Back" FooterButton3="Next" />
<ViewState ViewName="LoginView" Header="Log in" FooterButton1="Quit" FooterButton2="Back" FooterButton3="Next" />
<ViewState ViewName="InstallationView" Header="Installing..." FooterButton1="Cancel" FooterButton2="None" FooterButton3="Next" />
<ViewState ViewName="UpdateView" Header="Updating..." FooterButton1="Cancel" FooterButton2="None" FooterButton3="Next" />
<ViewState ViewName="FinishedView" Header="Finished!" FooterButton1="None" FooterButton2="None" FooterButton3="Finish" />
</ArrayOfViewState>
And when I match this in the code it looks like this (viewState.View is of type UserControl):
...
case "WelcomeView":
viewState.View = new WelcomeView();
...
As you can see I use the ViewName property in the XML to match and create my views (they also have a ViewModel, but that is taken care through XAML and the MVVM Light ViewModel Locator).
This solution technically allows the navigation to be changed somewhat without recompiling (for example you can shuffle the order any way you like), but there must be a better way to handle this than matching a string property. I've tried looking into serializing the User Control so that I could just load it along with the other properties, but so far I've had no luck. Any ideas on how to go about and improve/change this?
Thanks!
Indeed, there is a better way. :-)
Have a look at the Microsoft Extensibility Framework (MEF). It works very well with WPF and MVVM.
It allows you to easiliy compose application parts on-the-fly during runtine.
In short, you mark a class you want to be loaded somewhere else with an Attribute [Export]:
[Export(typeof(ViewContainer))]
public class ViewContainer
{
public string ViewName = "WelcomeView";
public string Header="Welcome to the Application"
// Do more stuff here
}
And in the class or assembly that should use the exported class you can load it with the [Import] Attribute:
public class ClassInOtherAssembly
{
[ImportMany]
internal ObservableCollection<ViewContainer> m_MyViews { get; set; }
// Do other stuff here
}
Depending on the architecture that you implement it might even be sufficient to use a 1-liner (!) to assemble all imported classes (this uses a different approach than the following referenced tutorial):
CompositionInitializer.SatisfyImports(this);
And that's it!
(Do not take these examples as is, I just wanted to get to the point. I recommend using
Properties instead of the strings and interfaces instead of the class export. You'll find plenty of more elegant snippets on the net. :-) )
Here you can find a tutorial to get you started:
Getting started with Managed Extensibility Framework (MEF)
Why not use reflection?
You could use Activator.CreateInstance and pass in your View's string:
string asmName = "YourAssembly";
string typeName = "YourViewName";
object obj = Activator.CreateInstance(asmName, typeName).Unwrap() as UserControl;
Related
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)
I am creating a xamarin behaviour to validate an email id, therefore I created the behaviour file and tried to localise it in XAML file but I get the below error
Xamarin.Forms.Xaml.XamlParseException: Position 12:10. Type
local:EmailBhvr not found in xmlns
clr-namespace:Validation.Helpers;assembly=Validation.Helpers
Namespace: Validation
Behaviour Code File: EmailBhvr
Here is my XAML code:
<? xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns = "http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:Validation;assembly=Validation"
x:Class="Validation.WelcomePage">
<StackLayout>
<Label Text = "Hello Xamarin" />
< Entry Placeholder="Enter your full name" x:Name="myName">
<Entry.Behaviors>
<local:EmailBhvr />
</Entry.Behaviors>
</Entry>
<Entry Placeholder = "Enter your email" x:Name="myEmail" />
<Entry Placeholder = "Enter password" x:Name="myPassword" IsPassword="True" />
<Entry Placeholder = "Confirm password" x:Name="myConfirmPassword" IsPassword="True" />
<Button Text = "Save" x:Name="SaveRegistration"/>
</StackLayout>
</ContentPage>
This could be related to a known linking issue - where Xamarin compiler ends up linking out classes (from external assemblies) that have references only in XAML.
Looks like EmailBhvr might be getting linked out by the compiler. There are couple of links that talk about this:
Extending Control plugins > Getting Started
Force assembly linking
There are a lot of options to resolve this:
Add a static Init method in each class as mentioned here in "Getting started" section here
// this ensures the class does not get
// linked out in the application we add this assembly to.
public static void Init() { }
Or, preserve code using preserve attributes on Android, and iOS
public class Example
{
[Android.Runtime.Preserve]
public Example ()
{
}
}
Or, use Custom linking.
Or, update project configuration to not link. Android, and iOS. Not a recommended option though.
I feel your XAML is clean. Looks error free. I think the problem is with the EmailBhvrclass in Validation. I suggest you to verify it. Make sure that the assembly name in XAML is also correct. XamlParseException can also occur with the incorrect assembly name..
I think you made some mistake while writing tags/property names in your EmailBhvr file.
Because of that you are getting parsing exception.
For the import to work, the class EmailBhvr must
be named "EmailBhvr"
reside in an assembly with an assembly name "Validation".
reside in the namespace "Validation" (check your class file)
Be especially careful with 2.:
If you use a shared Project the assembly name will be that of the platform project (e.g. it could be Validation.Droid / Validation.iOS). That can be fixed be giving both the same Assembly name (in project properties). For example "Validation.Platform" and change the xaml namespace import accordingly
Please Make sure that the assembly name in XAML is correct. it should be like this xmlns:local="clr-namespace:ProjectNamspace.Validation;assembly=ProjectNamspace.Validation"
I'm trying to build an android binding library for LINE SDK (a SNS service popular in Asia area). I added the jar file from the SDK into my project but it fails to compile because the creators of the SDK just happen to use the same name for a class and its property.
This is part of the C# code that the project generated from the jar file
public partial class AccessToken : global::Java.Lang.Object {
// Metadata.xml XPath field reference: path="/api/package[#name='jp.line.android.sdk.model']/class[#name='AccessToken']/field[#name='accessToken']"
[Register ("accessToken")]
public string AccessToken {
get { /* ... */ }
set { /* ... */ }
}
//...
}
Now simply put, I want to know how I can rename this property "AccessToken". I tried adding some commands in Metadata.xml but it had no effect. Here's a line of what I've written for reference
<attr path="/api/package[#name='jp.line.android.sdk.model']/class[#name='AccessToken']/field[#name='accessToken']" name="propertyName">AccessTokenString</attr>
I'd be grateful if somebody could help me out. I'm totally lost here.
What #SuavePirate suggests will not work completely (at least not now).
If you use name it will also rename the Register attribute parameter as [Register ("AccessTokenString")] instead of [Register ("accessToken")]. So as it states here it will be no longer possible for the Xamarin.Android binding class to access that property because it is not bound to an existent Java member.
To properly change the managed name of a wrapped member, it is necessary to set the managedName attribute, i.e.:
<attr path="/api/package[#name='jp.line.android.sdk.model']/class[#name='AccessToken']/field[#name='accessToken']" name="managedName">AccessTokenString</attr>
I believe the only issue is that "propertyName" should just be "name":
<attr path="/api/package[#name='jp.line.android.sdk.model']/class[#name='AccessToken']/field[#name='accessToken']" name="name">AccessTokenString</attr>
I'm using Systeml.Xaml to create a custom layout engine that is based on XAML. Since it's cross-platform, the 95% of the types are defined in Portable Class Libraries, it's impossible to use the XmlnsDefinitionAttribute to "decorate" the default assemblies.
I would like to use XamlReader to read my XAML-formatted files. The problem is that my the objects belong to several namespaces. Currently, if I want the XamlReader to be able to instantiate each one, I have to specify from which namespace each object like this:
<Window Title="Title" xmlns="clr-namespace:Perspex.Win32;assembly=Perspex.Win32"
xmlns:r="clr-namespace:Perspex;assembly=Perspex.Base">
<Window.Content>
<TextBlock xmlns="clr-namespace:Perspex.Controls;assembly=Perspex.Controls" Text="{r:Binding}" />
</Window.Content>
</Window>
How can I make the XamlReader to have a set of default namespaces that are implicit so they are discovered without specifying namespaces?
Edit: The current code I have is this. I'm using at XamlXmlReader to do it.
private static object Load(XmlReader reader)
{
XamlXmlReader xamlXmlReader = new XamlXmlReader(reader, xamlSchemaContext);
XamlObjectWriter writer = new XamlObjectWriter(
xamlSchemaContext,
new XamlObjectWriterSettings
{
XamlSetValueHandler = SetValue,
});
while (xamlXmlReader.Read())
{
writer.WriteNode(xamlXmlReader);
}
object result = writer.Result;
return result;
}
As you can see, I tried with a XamlSchemaContext with some of the default assemblies, but it doesn't work :(
Have you considered using the assembly level XmlnsDefinitionAttribute? It allows you to define one xmlns that maps to several CLR namespaces in one or more assemblies.
Thanks, Rob
This is copied example from:
How to read custom config section in app.config in c#
I want to read following custom section from app.config:
<StartupFolders>
<Folders name="a">
<add folderType="Inst" path="c:\foo" />
<add folderType="Prof" path="C:\foo1" />
</Folders>
<Folders name="b">
<add folderType="Inst" path="c:\foo" />
<add folderType="Prof" path="C:\foo1" />
</Folders>
</StartupFolders>
And this is my case too. However, I don't want to create custom class for handling values, defining this class in web.config, and then finally using it. It is heavy-weight for my needs.
Instead I would like to do something very simple -- retrieve a section as XML. Then I could use regular Linq.Xml to parse it. This way, I don't need to create new classes per each section, I don't need to declare them. For my purpose it is sufficient on one hand, and minimal at the other (I do it once, key-value mapper for nested sections). I.e. perfect.
The only missing piece is (my question) -- how to get a web.config section as XML?
Note about the section:
it cannot be encoded, because it has to be edited by hand
it cannot be serialized for the same reason
So I am not looking for a workaround how to squeeze entire section as value in appSettings, but I am really looking for a method to get proper section as XML.
I would like to get it from ConfigManager (!), because this way I don't have to deal with resolving which web.config should I read, etc. I.e. less chance to make mistake than mimicing web.config precedence manually.
Forgive me for reminding this, but please avoid "answers", you shouldn't do this, use custom class per each section, etc. I already considered this, and opted against it.
I think you either have to do it manually and load the Web config into memory:
XmlDocument doc = new XmlDocument();
doc.Load(Server.MapPath("~/Web.config"));
Or you will need to create the custom configuration sections you want to avoid.
You can define a re-usable custom config section that exposes the section XML as you desire. The key is, that you don't have to define a different class for each custom config section.
For clarity, my project namespace is "ConsoleApp1" as is the assembly name (this appears in type definitions).
First, create a custom config section that exposes the XML reader:
public class XmlConfigSection : ConfigurationSection
{
public XmlReader Xml { get; private set; }
override protected void DeserializeSection(XmlReader reader)
{
Xml = reader;
}
}
You can then define any of your custom sections to use this class in the app.config:
<configSections>
<section name="StartupFolders" type="ConsoleApp1.XmlConfigSection, ConsoleApp1" />
<section name="AnotherCustomSection" type="ConsoleApp1.XmlConfigSection, ConsoleApp1" />
</configSections>
Then in your code, you can access raw XmlReader of the config section like this:
var xmlReader = (ConfigurationManager.GetSection("StartupFolders") as XmlConfigSection).Xml;
If you then want an XML string instead of the reader you can do something like this (though I'd suggest sticking with the XmlReader):
StringBuilder sb = new StringBuilder();
while (xmlReader.Read())
sb.AppendLine(xmlReader.ReadOuterXml());
var xmlStr = sb.ToString();
Totally untested but could you use something like this? :
ConfigurationSection exampleSection =
(ConfigurationSection)ConfigurationManager
.GetSection("system.web/exampleSection");
Then possibly use exampleSection.ElementInformation to get more info?