Understanding Uncompiled XAML to design dynamic UI in WPF


Introduction: This is a specialized approach that makes sense in certain scenarios where we need highly dynamic user interfaces. We load part of the user interface from a XAML file at runtime using the XamlReader class from the System.Windows.Markup namespace.

One of the most interesting ways to use XAML is to parse it on the fly with the XamlReader.

Let us create a project and load external XAML by using XamlReader in some other XAML window.

Step 1: Add a window application and name is NonCompiledXaml

Step 2: Open Window1.xaml and delete what visual studio adds up and write below code;

<DockPanel xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
    <Button Name="button1" Margin="30">Please click me</Button>
</DockPanel>

Here I have just created a simple button inside a dockpanel.

Step 3: At runtime we can load this content in a live window to create the same window. So open window1.xmal.cs and write the below code.

public partial class Window1 : Window
    {
        private Button button1;

        public Window1(string xamlFile)
        {
            InitializeComponent(xamlFile);
        }

        private void InitializeComponent(string xamlFile)
        {
            //Configure the form
            this.Width = this.Height = 285;
            this.Left = this.Top = 100;
            this.Title = "Dynamically Loaded XAML";

            //Load the XAML content from external file
            DependencyObject rootElement;
            using (FileStream fs = new FileStream(xamlFile,FileMode.Open))
            {
                rootElement = (DependencyObject)XamlReader.Load(fs);
            }
            //Insert the markup into this window.
            this.Content = rootElement;

            FrameworkElement frameworkElement = (FrameworkElement)rootElement;
            button1 = (Button)frameworkElement.FindName("button1");
 
            //Write up the event handler
            button1.Click+=new RoutedEventHandler(button1_Click);
        }
        private void button1_Click(object sender, RoutedEventArgs e)
        {
            button1.Content = "Thank you.";
        }
    }

Here is the explaination of the code:

  1. Here, the InitializeComponent() method opens a FileStream on the Window1.xml file. It then uses the Load() method of the XamlReader to convert the content in this file into a DependencyObject, which is the base from which all WPF controls derive. This Dependency- Object can be placed inside any type of container in this example it's used as the content for the entire form.

  2. To manipulate the button, we need to find the corresponding control object in the dynamically loaded content. The LogicalTreeHelper serves this purpose because it has the ability to search an entire tree of control objects, digging down as many layers as necessary until it finds the object with the name we've specified. An event handler is then attached to the Button.Click event.

  3. Another alternative is to use the FrameworkElement.FindName() method. In this example, the root element is a DockPanel object. Like all the controls in a WPF window, DockPanel derives from FrameworkElement. That means we can replace this code;

    button1 = (Button)LogicalTreeHelper.FindLogicalNode(rootElement, "button1");

    This is equivalent to below code;

    FrameworkElement frameworkElement = (FrameworkElement)rootElement;
                button1 = (Button)frameworkElement.FindName("button1");

Step 4: Let us add a class program.cs to write application statup.

public class Program:Application
    {
        [STAThread]
        static void Main()
        {
            Program app = new Program();
            app.ShutdownMode = ShutdownMode.OnLastWindowClose;

            //Our first approach: window with XAML Content
            Window1 win = new Window1("Window1.xaml");
            win.Show();
        }
    }

Step 5: Run the application and see that now we dynamically loading.

Understanding Uncompiled XAML to design dynamic UI in WPF

Step 6: Here we saw that we're loading an element—the DockPanel object—from the XAML file. Alternatively, we could load an entire XAML window In this case, we would cast the object returned by XamlReader.Load() to the Window type and then call its Show() or ShowDialog() method to show it.

So let create a window and load entire XAML window and see how we can cast it.

Add a window and name it as Xaml2009.xaml

Step 7: Write below class inside Xaml2011.xaml. Delete all default XAML added by visual studio.

namespace NonCompiledXaml
{
    public class Xaml2009Window : Window
    {       
        public static Xaml2009Window LoadWindowFromXaml(string xamlFile)
        {
            // Get the XAML content from an external file.           
            using (FileStream fs = new FileStream(xamlFile, FileMode.Open))
            {
                Xaml2009Window window = (Xaml2009Window)XamlReader.Load(fs);
                return window;
            }           
        }

        private void lst_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            MessageBox.Show(e.AddedItems[0].ToString());
        }
    }
 
    public class Person
    {
        public string FirstName { get; set; }
        public string LastName { get; set; }

        public Person(string firstName, string lastName)
        {
            FirstName = firstName;
            LastName = lastName;
        }

        public override string ToString()
        {
            return FirstName + " " + LastName;
        }
    }

}

In this case, we would cast the object returned by XamlReader.Load() to the Window type and then call its Show() or ShowDialog() method to show it.

Step 8: Add below XAML in Xaml2009.xaml

<local:Xaml2009Window
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:local="clr-namespace:NonCompiledXaml;assembly=NonCompiledXaml"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:sys="clr-namespace:System;assembly=mscorlib"
            Width="300" Height="300" Title
="XAML 2009">
<StackPanel Margin="10">
    <Label Target="{x:Reference txtFirstName}">_FirstName</Label>
    <TextBox x:Name="txtFirstName" />
    <Label Margin="0,10,0,0" Target="{x:Reference txtLastName}">_LastName</Label>
    <TextBox x:Name="txtLastName" />

    <ListBox Margin="0,25,0,0" SelectionChanged="lst_SelectionChanged">
        <x:String>Item One</x:String>
        <x:String>Item Two</x:String>
        <x:String>Item Three</x:String>
        <local:Person>
            <x:Arguments>
                <x:String>Joe</x:String>
                <x:String>McDowell</x:String>
            </x:Arguments>
        </local:Person>
        <sys:Guid x:FactoryMethod="NewGuid"></sys:Guid>
    </ListBox>
       
</StackPanel>
</
local:Xaml2009Window>

Step 9:  Update program.cs

public class Program : Application
    {
        [STAThread()]
        static void Main()
        {
            // Create a program that will show two different windows,
            // and will wait until both are closed before ending.
            Program app = new Program();
            app.ShutdownMode = ShutdownMode.OnLastWindowClose;

            // First approach: window with XAML content.
            Window1 window1 = new Window1("Window1.xaml");
            window1.Show();

            // Second approach: XAML for complete window.           
            Xaml2009Window window2 = Xaml2009Window.LoadWindowFromXaml("Xaml2009.xaml");
            window2.Show();

            app.Run();
        }
    }


Step 10: Run the application

Understanding Uncompiled XAML to design dynamic UI in WPF

When to use this approach : For example, we could create an all-purpose survey application that reads a form file from a web service and then displays the corresponding survey controls (labels, text boxes, check boxes, and so on). The form file would be an ordinary XML document with WPF tags, which we load into an existing form using the XamlReader. To collect the results once the survey is filled out, we simply need to enumerate over all the input controls and grab their content.

Conclusion: Obviously, loading XAML dynamically won't be as efficient as compiling the XAML to BAML and then loading the BAML at runtime, particularly if our user interface is complex.

However, it opens up a number of possibilities for building dynamic user interfaces.

Thanks for reading