Loading XAML visual elements in runtime and late binding

IMPORTANT!
The content below is a documentation of a Windows Phone 8.1 Silverlight app. If you are using other platforms, some resources used here might not be available. Refer to MSDN for the equivalent resources in your platform

One of the projects I’m working on at the moment is a software solution that enables smartphones to operate as remote controller and motion-capturing device. It was supposed to be our team’s submission to Microsoft’s Imagine Cup competition in innovation category. Unfortunately, it was struck down in the prelims and development was shifted to Android OS.

As persistent as I can be, I insisted on keeping development for Windows Phone OS alive. The latest implementation is a self-updater which allows the app to update its interface according to the working application on a remote server.

The idea is simple:

  1. Save a visual element to XML file
  2. Reconstruct the same element as XAML from XML file
  3. Perform late binding to codebehind

EXPORT A VISUAL ELEMENT TO XML

The visual element is exported to XML files in two parts: the UIElement object and binding parameters. For the UIElement object, I convert the XAML to plain text, which can later be read by XamlReader.Load and reconstruct into FrameworkElement objects.

Code binding was a bit more complicated as XamlReader does not allow direct binding in XAML scripts. That needs to be done manually in the codebehind. Sadly, I have no idea how to load codebehind in runtime and since each class comes with its own set of events and properties, a generic object class cannot be used here without going unsafe. It would be disastrous to address a non-existent TextBlock.Text property of a Button-class object! This just shows just how late binding works: assume the class of an unknown object to be one of the expected classes and to have expected properties of the said class.

Serialization classes are useful, they recreate a wrapper class object I can address easily. Of course, I can alternatively address each XML tag using XElement class‘s methods.

I created a wrapper class called “XmlData”, which is basically a struct containing the corresponding descriptor parameters in the XML file: Name, Type, Handler within its sub-class XmlBindingData (the main XmlData class can also contain page-level data for other purposes such as changing the Title to the loaded XML’s ModuleName).

/*Custom wrapper class for serialization
This class must exist in Serializer and Deserializer projects.
@author: Fujihita fujihita.wordpress.com
@platform: Any*/

    public class XmlData
    {
        public byte[] XAML {get; set;}
        public string ModuleName {get; set;}
        public List<XmlBindingData> data {get; set;}
    }
    public class XmlBindingData
    {
        public string name { get; set; }
        public string type { get; set; }
        public string handler { get; set; }
    }

I resorted to Windows Form Application for serialized XML generation. I’d have to say the Stream and XmlWriter classes are a mess from one platform to another and I won’t be posting the full source code for getting user input.

/*XmlSerialize class
This class contains static method to export to XML format a XmlData object
which contains XAML and control data defined by user input
@author: Fujihita, fujihita.wordpress.com
@platform: Windows Form Application*/

public class XmlSerialize()
{
        public static void Serialize(XmlData myObject, string path)
        {
            // Insert code to set properties and fields of the object.
            XmlSerializer mySerializer = new XmlSerializer(typeof(XmlData));
            // To write to a file, create a writer object.
            FileStream myWriter = new FileStream(path, FileMode.OpenOrCreate);
            mySerializer.Serialize(myWriter, myObject);
            myWriter.Close();
        }
}

It should be noted, however, that the XAML string is now stored as byte array using UTF8 Encoding.

RECONSTRUCT A VISUAL ELEMENT

For illustration purposes, I’ll be using the serializer and generates the following Sample.xml file

<?xml version="1.0"?>
<XmlData xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <XAML>PFN0YWNrUGFuZWwgeG1sbnM9Imh0dHA6Ly9zY2hlbWFzLm1pY3Jvc29mdC5jb20vd2luZngvMjAwNi94YW1sL3ByZXNlbnRhdGlvbiIgeG1sbnM6eD0iaHR0cDovL3NjaGVtYXMubWljcm9zb2Z0LmNvbS93aW5meC8yMDA2L3hhbWwiPg0KICA8VGV4dEJsb2NrIHg6TmFtZT0iVGV4dDEiIEhvcml6b250YWxBbGlnbm1lbnQ9IkNlbnRlciIgVGV4dD0iIj48L1RleHRCbG9jaz4NCiAgPFRleHRCbG9jayB4Ok5hbWU9IlRleHQyIiBIb3Jpem9udGFsQWxpZ25tZW50PSJDZW50ZXIiIE1hcmdpbj0iMCwyMCwwLDIwIiBUZXh0PSIiPjwvVGV4dEJsb2NrPg0KICA8QnV0dG9uIENvbnRlbnQ9IkJ1dHRvbjEiIHg6TmFtZT0id19idG4iIEhvcml6b250YWxBbGlnbm1lbnQ9IkNlbnRlciIgTWFyZ2luPSIxMzcsMjAiIEhlaWdodD0iMTAwIiBXaWR0aD0iMTgyIiBUYWc9IjB4MTEiLz4NCiAgPEJ1dHRvbiBDb250ZW50PSJCdXR0b24yIiB4Ok5hbWU9ImVfYnRuIiBIb3Jpem9udGFsQWxpZ25tZW50PSJDZW50ZXIiIE1hcmdpbj0iMTM1LDIwIiBIZWlnaHQ9IjEwMCIgV2lkdGg9IjE4NiIgVGFnPSIweDFFIi8+DQo8L1N0YWNrUGFuZWw+</XAML>
  <ModuleName>Test app</ModuleName>
  <data>
    <XmlBindingData>
      <name>e_btn</name>
      <type>Button</type>
      <handler>ScanCode</handler>
    </XmlBindingData>
    <XmlBindingData>
      <name>e_btn</name>
      <type>Button</type>
      <handler>ScanCode</handler>
    </XmlBindingData>
  </data>
</XmlData>

The other half of the XmlSerialize class in the Windows Phone app contains Deserializer method defined as follow:

/*XmlSerialize class
This class contains a static method to import a XML file from path
and return xmlData class to be used in reconstruction
@author: Fujihita, fujihita.wordpress.com
@platform: Windows Phone 8.1 Silverlight*/

public class XmlSerialize()
{
         public static XmlData Deserialize()
        {
            XmlData myObject;
            // Construct an instance of the XmlSerializer with the type
            // of object that is being deserialized.
            XmlSerializer mySerializer =
            new XmlSerializer(typeof(XmlData));
            // To read the file, create a FileStream.
            FileStream myFileStream =
            new FileStream("XML/Sample.xml", FileMode.Open);
            // Call the Deserialize method and cast to the object type.
            myObject = (XmlData)mySerializer.Deserialize(myFileStream);
            return myObject;
        }
}

The reconstruction code is quite simple. I only need to call XamlReader, feed the XAML string to it in order to get the collection of visual elements. Then, I can add the reconstructed elements to a StackPanel named “ContentPanel” which exists in all default XAML pages. This code snippet can be inserted directly into a page’s constructor or wherever I want to load the elements:

//Read new visual content from plain text
            XmlData data = XmlSerialize.Deserialize();
            FrameworkElement NewContent = (FrameworkElement)XamlReader.Load(Encoding.UTF8.GetString(data.XAML, 0, data.XAML.Length));
//Add visual elements to current page's ContentPanel
            ContentPanel.Children.Clear();
            ContentPanel.Children.Add(newContent as UIElement); 

This yields the interface, buttons and all. Except that, the controls don’t work without handlers.

CODE BINDING

Take note how I first cast the visual elements returned by XamlReader to FrameworkElement class. While it adds another layer of type casting, the FrameworkElement.FindName method helps greatly in code binding. I only need to know the names of the object, which can be assigned directly in XAML script since XamlReader permits this, to address the right element. Tag property of the element can optionally be used to store data.

Now that I also have Type, Name and Handler descriptor loaded into xmlData collection, I only need a custom class library containing every conceivable event handlers for the project which take the descriptors and assign each object to the correct handler accordingly:

/*xamlBinder class library (.dll)
This class library contains logic routing, binding method
and all event handlers required by the project
This is snippet a minimal example
@author: Fujihita, fujihita.wordpress.com
@platform: Windows Phone 8.1 Silverlight*/

public class xamlBinder
{  
        public void assignHandler(string name, string type, string handler, FrameworkElement content)
        {
            switch (type)
            {
                case "Button":
                {
                    addInputBtn((Button)content.FindName(name), handler);
                    break;
                }
                case "CheckBox":
                {
                    break;
                }
                case "Message":
                {
                    break;
                }
            }
        }

        private void addInputBtn(Button targetButton, string handler)
        {
            switch (handler)
            {
                case "ScanCode":
                    {
                        targetButton.Click += new targetButton.Click += new RoutedEventHandler(CustomEventHandler)
                        break;
                    }
                case "LeftMouseClick":
                    {
                        // binding code here
                        break;
                    }
                case "RightMouseClick":
                    {
                        // binding code here
                        break;
                    }
                default:
                    {
                        // binding code here
                        break;
                    }
            }
        }

        public void CustomEventHandler(object sender, RoutedEventArgs e)
        {
             // The handler toggles the button's text with the text stored in Tag property. 
             Button btn = (Button)sender;
             string temp = (string)btn.Content;
             btn.Content = btn.Tag;
             btn.Tag = temp;
        }
}

Finally, add this snippet to wherever you loaded the visual elements in order to bind the elements to handlers in class library.

            xamlBinder myBinder = new xamlBinder();
            foreach (XmlBindingData xbd in data.data)
            {
                myBinder.assignHandler(xbd.name, xbd.type, xbd.handler, newContent);
            }

APPENDIX
For those who have difficulties writing a custom serializer, the second approach as mentioned in the first section might become handy. I have the following method added to wherever I want to load the elements (i.e. MainPage.xaml.cs file, MainPage constructor):

/*XML importer method
This method accesses XML file from path
and extracts values from XML Tags
@author Fujihita fujihita.wordpress.com
@platform Windows Phone 8.1 Silverlight*/

private void xmlLoader()
{
            Uri uri = new Uri("XML/Sample2.xml", UriKind.Relative);
            StreamResourceInfo strm = Application.GetResourceStream(uri);
            XElement x = XElement.Load(strm.Stream);

            xamlBinder myBinder = new xamlBinder();
            string Name, Type, Handler;

            //Process Info &amp;amp;amp;gt; ModuleName tag (unique instance)
            string ModuleName = x.Element("ModuleName").Value;
            
            //Process XAML
            string XAMLString = x.Element("XAML").Value;
            FrameworkElement NewContent = (FrameworkElement)XamlReader.Load(XAMLString);
            //Add visual elements to current page's ContentPanel
            ContentPanel.Children.Clear();
            ContentPanel.Children.Add(newContent as UIElement);

            //Process Code &amp;amp;amp;gt; Object tags (multiple instances)
            IEnumerable elList1 = (from el in x.Elements("data").Elements("XmlBindingData") select el);
            foreach (XElement el in elList1)
            {
                Name = el.Element("name").Value
                Type = el.Element("type").Value
                Handler = el.Element("handler").Value;

                myBinder.assignHandler(Name, Type, Handler, newContent);
            }
}

This loads a XML file from path and calls the class library instantly using xElement class methods. xamlBinder class library is still needed and the XML file is stored plainly without serialization or custom wrapper class. Find below the file Sample2.xml:

<?xml version="1.0" encoding="utf-8" ?>
<XmlData xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<StackPanel xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
  <TextBlock x:Name="Text1" HorizontalAlignment="Center" Text=""></TextBlock>
  <TextBlock x:Name="Text2" HorizontalAlignment="Center" Margin="0,20,0,20" Text=""></TextBlock>
  <Button Content="Button1" x:Name="w_btn" HorizontalAlignment="Center" Margin="137,20" Height="100" Width="182" Tag="0x11"/>
  <Button Content="Button2" x:Name="e_btn" HorizontalAlignment="Center" Margin="135,20" Height="100" Width="186" Tag="0x1E"/>
</StackPanel>
</XAML>
  <ModuleName>Test app</ModuleName>
  <data>
    <XmlBindingData>
      <name>e_btn</name>
      <type>Button</type>
      <handler>ScanCode</handler>
    </XmlBindingData>
    <XmlBindingData>
      <name>e_btn</name>
      <type>Button</type>
      <handler>ScanCode</handler>
    </XmlBindingData>
  </data>
</XmlData>

Which is also the equivalent of Sample.xml from before. This file must be written manually.

Advertisements

Published by

fujihita

Self-learner, designer, author and programmer.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s