Skip to content
Bruno Sonnino
Menu
  • Home
  • About
Menu

Loading XAML Dynamically – Part 1 – Loading Views Dynamically

Posted on 21 July 2016

Most of the time, our design is fixed and we don’t have to change it while it’s executing, but sometimes this is not true: we need to load the files dynamically, due to a lot of factors:

  • We have the same business rules for the project but every client needs a different UI
  • The design can be different, depending on some conditions
  • We are not sure of the design and want to make changes at runtime and see how they work

When this happens, WPF is a huge help, as it can load its design dynamically and load the XAML files at runtime.

Preparing the application

When we want to load the XAML dynamically, we must ensure that all business rules are separated from the UI – we are only loading a new UI, the business rules remain the same. A good fit for that is the MVVM pattern. With it, the business rules are completely separated from the UI and you can use Data Binding to bind the data to the UI. So, when you are designing a dynamic application you should use the MVVM pattern.

One other preparation is to know how will your application be designed: will it run on a single window, changing the content, or will it have multiple windows? If it runs on a single window, you should create a shell with a content control and load all dynamic files in that control. If the application will have multiple windows, you should create a new window every time there is new content.

Once you have the application ready, with the business rules separated from the UI and the kind of UI defined, you can load your XAML dynamically.

Loading the XAML Dynamically

WPF has a simple way to load XAML dynamically. It has a class, XamlReader that has methods to load XAML dynamically and instantiate a new control. It can be used like this:

var mainContent = XamlReader.Parse(@"<Button Width=""85"" Height=""35"" Content=""Test""
    xmlns=""http://schemas.microsoft.com/winfx/2006/xaml/presentation""
    xmlns:x = ""http://schemas.microsoft.com/winfx/2006/xaml"" />") as Button;
MainGrid.Children.Add(mainContent);
C#

In this example, we are using the Parse method to a XAML component from a string. We could load a XAML file dynamically using the Load method:

using (FileStream fs = new FileStream("Button.xaml", FileMode.Open))
{
  mainContent = XamlReader.Load(fs) as Button;
  MainGrid.Children.Add(mainContent);
}
C#

Note that both Load and Parse return an object and that must be typecasted to the real type of the control.

Creating a real world dynamic application

In a real world application, we can have different uses to this feature:

  • Use the same data with different layouts – in this case, we have a single viewmodel and the views connect to the same data, with different layouts
  • Use different formatting options to the same view. This is used when you want to customize the view presentation (color, layout) for different clients

For the first usage, you must create completely different views for the same data and use the MVVM pattern to connect to the data.

For the second usage, you can use the same views, but connect to different Resource Dictionaries, so the new styles can be implemented. In this post, we will implement different views for the same viewmodel.

The project I’ll be using will use a Customers list, loaded from an XML file. The app uses the MVVM pattern (I’ve chosen not to use any MVVM framework, but a simple homemade MVVM framework, with a base ViewModelBase class and a RelayCommand class, to build the infrastructure).

The Customer’s list is loaded in the CustomersViewModel, which has many properties (and commands) to interact with the views:

public class CustomersViewModel : ViewModelBase
{
    readonly CustomerRepository customerRepository = new CustomerRepository();
    private readonly ObservableCollection<CustomerViewModel> customers;

    public CustomersViewModel()
    {
        var customerViewModels = customerRepository.Customers.Select(c => new CustomerViewModel(c));
        customers = new ObservableCollection<CustomerViewModel>(customerViewModels);
        customerView = (CollectionView)CollectionViewSource.GetDefaultView(customers);
    }

    private CustomerViewModel selectedCustomer;
    public CustomerViewModel SelectedCustomer
    {
        get { return selectedCustomer; }
        set
        {
            selectedCustomer = value;
            RaisePropertyChanged("SelectedCustomer");
        }
    }

    private ICommand goFirstCommand;
    public ICommand GoFirstCommand => goFirstCommand ?? (goFirstCommand = new RelayCommand(GoFirst));

    private void GoFirst(object obj)
    {
        customerView.MoveCurrentToFirst();
        SelectedCustomer = (CustomerViewModel)customerView.CurrentItem;
    }

    private ICommand goPrevCommand;
    public ICommand GoPrevCommand => goPrevCommand ?? (goPrevCommand = new RelayCommand(GoPrev));

    private void GoPrev(object obj)
    {
        customerView.MoveCurrentToPrevious();
        SelectedCustomer = (CustomerViewModel)customerView.CurrentItem;
    }

    private ICommand goNextCommand;
    public ICommand GoNextCommand => goNextCommand ?? (goNextCommand = new RelayCommand(GoNext));

    private void GoNext(object obj)
    {
        customerView.MoveCurrentToNext();
        SelectedCustomer = (CustomerViewModel)customerView.CurrentItem;
    }

    private ICommand goLastCommand;
    public ICommand GoLastCommand => goLastCommand ?? (goLastCommand = new RelayCommand(GoLast));

    private void GoLast(object obj)
    {
        customerView.MoveCurrentToLast();
        SelectedCustomer = (CustomerViewModel)customerView.CurrentItem;
    }
    private string searchText;
    public string SearchText
    {
        get { return searchText; }
        set
        {
            searchText = value;
            RaisePropertyChanged("SearchText");
        }
    }

    public ObservableCollection<CustomerViewModel> Customers
    {
        get { return customers; }
    }

    private ICommand addCommand;
    public ICommand AddCommand
    {
        get { return addCommand ?? (addCommand = new RelayCommand(AddCustomer, null)); }
    }

    private void AddCustomer(object obj)
    {
        var customer = new Customer();
        var customerViewModel = new CustomerViewModel(customer);
        customers.Add(customerViewModel);
        customerRepository.Add(customer);
        SelectedCustomer = customerViewModel;
    }

    private ICommand removeCommand;
    public ICommand RemoveCommand
    {
        get {
            return removeCommand ??
                   (removeCommand = new RelayCommand(RemoveCustomer, c => SelectedCustomer != null));
        }
    }

    private void RemoveCustomer(object obj)
    {
        customerRepository.Remove(SelectedCustomer.Customer);
        customers.Remove(SelectedCustomer);
        SelectedCustomer = null;
    }

    private ICommand saveCommand;
    public ICommand SaveCommand
    {
        get { return saveCommand ?? (saveCommand = new RelayCommand(SaveData, null)); }
    }

    private void SaveData(object obj)
    {
        customerRepository.Commit();
    }

    private ICommand searchCommand;
    private ICollectionView customerView;

    public ICommand SearchCommand
    {
        get
        {
            if (searchCommand == null)
                searchCommand = new RelayCommand(SearchData, null);
            return searchCommand;
        }
    }

    private void SearchData(object obj)
    {
        customerView.Filter = !string.IsNullOrWhiteSpace(SearchText) ?
            c => ((CustomerViewModel)c).Country.ToLower()
                           .Contains(SearchText.ToLower()) :
            (Predicate<object>)null;
    }
}
C#

The main property is Customer, that has the list of CustomerViewModels that will be shown in the master views and SelectedCustomer, that has the selected customer that will be edited in the details views. There are a lot of commands to filter the list, move the selected record, add, remove and save the data.

The CustomerViewModel class is:

public class CustomerViewModel : ViewModelBase
{
    public Customer Customer { get; private set; }

    public CustomerViewModel(Customer cust)
    {
        Customer = cust;
    }

    public string CustomerId
    {
        get { return Customer.CustomerId; }
        set
        {
            Customer.CustomerId = value;
            RaisePropertyChanged("CustomerId");
        }
    }

    public string CompanyName
    {
        get { return Customer.CompanyName; }
        set
        {
            Customer.CompanyName = value;
            RaisePropertyChanged("CompanyName");
        }
    }

    public string ContactName
    {
        get { return Customer.ContactName; }
        set
        {
            Customer.ContactName = value;
            RaisePropertyChanged("ContactName");
        }
    }

    public string ContactTitle
    {
        get { return Customer.ContactTitle; }
        set
        {
            Customer.ContactTitle = value;
            RaisePropertyChanged("ContactTitle");
        }
    }

    public string Region
    {
        get { return Customer.Region; }
        set
        {
            Customer.Region = value;
            RaisePropertyChanged("Region");
        }
    }

    public string Address
    {
        get { return Customer.Address; }
        set
        {
            Customer.Address = value;
            RaisePropertyChanged("Address");
        }
    }

    public string City
    {
        get { return Customer.City; }
        set
        {
            Customer.City = value;
            RaisePropertyChanged("City");
        }
    }

    public string Country
    {
        get { return Customer.Country; }
        set
        {
            Customer.Country = value;
            RaisePropertyChanged("Country");
        }
    }

    public string PostalCode
    {
        get { return Customer.PostalCode; }
        set
        {
            Customer.PostalCode = value;
            RaisePropertyChanged("PostalCode");
        }
    }

    public string Phone
    {
        get { return Customer.Phone; }
        set
        {
            Customer.Phone = value;
            RaisePropertyChanged("Phone");
        }
    }

    public string Fax
    {
        get { return Customer.Fax; }
        set
        {
            Customer.Fax = value;
            RaisePropertyChanged("Fax");
        }
    } 
}
C#

It doesn’t do anything but expose the Customer’s properties. With this infrastructure in place, I can create my view loading. I did this in the code behind, the code is very simple, it just sets the data context for the window and loads the correct file, depending on the selection of a combobox:

public MainWindow()
{
    InitializeComponent();
    DataContext = new CustomersViewModel();
}

private void SelectedViewChanged(object sender, SelectionChangedEventArgs e)
{
    var viewIndex = (sender as ComboBox).SelectedIndex;
    FrameworkElement view = null;
    switch (viewIndex)
    {
        case 0:
            view = LoadView("masterdetail.xaml");
            break;
        case 1:
            view = LoadView("detail.xaml");
            break;
        case 2:
            view = LoadView("master.xaml");
            break;
    }
    MainContent.Content = view;
}

private FrameworkElement LoadView(string fileName)
{
    using (FileStream fs = new FileStream(fileName, FileMode.Open))
    {
        return XamlReader.Load(fs) as FrameworkElement;
    }
}
C#

The XAML for the main window is:

<Grid>
    <Grid.RowDefinitions>
        <RowDefinition Height="40"/>
        <RowDefinition Height="*"/>
    </Grid.RowDefinitions>
    <ComboBox Height="30" Width="200" SelectionChanged="SelectedViewChanged" HorizontalAlignment="Left" Margin="5">
        <ComboBoxItem>Master/Detail</ComboBoxItem>
        <ComboBoxItem>Detail</ComboBoxItem>
        <ComboBoxItem>Master</ComboBoxItem>
    </ComboBox>
    <ContentControl Grid.Row="1" x:Name="MainContent"/>
</Grid>
XML

As you can see, the main window is very simple, it just has the combobox to select the view and a ContentControl, where the new view is loaded. An example of the dynamic view is the Master/Detail view:

<UserControl xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             mc:Ignorable="d"
             d:DesignHeight="600" d:DesignWidth="800">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="40" />
            <RowDefinition Height="*" />
            <RowDefinition Height="2*" />
            <RowDefinition Height="50" />
        </Grid.RowDefinitions>
        <StackPanel Orientation="Horizontal">
            <TextBlock Text="Country" VerticalAlignment="Center" Margin="5"/>
            <TextBox Height="25"
                     VerticalAlignment="Center" Margin="5,3" Width="250" Text="{Binding SearchText, Mode=TwoWay}"  />
            <Button Content="Search" Width="75" Height="25" Margin="10,5" VerticalAlignment="Center" 
                    Command="{Binding SearchCommand}" />
        </StackPanel>
        <DataGrid AutoGenerateColumns="False" x:Name="master" CanUserAddRows="False" CanUserDeleteRows="True" Grid.Row="1"
                  ItemsSource="{Binding Customers}" SelectedItem="{Binding SelectedCustomer, Mode=TwoWay}" >
            <DataGrid.Columns>
                <DataGridTextColumn x:Name="customerIDColumn" Binding="{Binding Path=CustomerId}" Header="Customer ID" Width="80" />
                <DataGridTextColumn x:Name="companyNameColumn" Binding="{Binding Path=CompanyName,ValidatesOnDataErrors=True}" Header="Company Name" Width="300" />
                <DataGridTextColumn x:Name="cityColumn" Binding="{Binding Path=City}" Header="City" Width="100" />
                <DataGridTextColumn x:Name="countryColumn" Binding="{Binding Path=Country}" Header="Country" Width="100" />
            </DataGrid.Columns>
        </DataGrid>
        <Grid DataContext="{Binding SelectedCustomer}" Grid.Row="2">
            <Grid Name="grid1" >
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="Auto" />
                    <ColumnDefinition Width="*" />
                </Grid.ColumnDefinitions>
                <Grid.RowDefinitions>
                    <RowDefinition Height="Auto" />
                    <RowDefinition Height="Auto" />
                    <RowDefinition Height="Auto" />
                    <RowDefinition Height="Auto" />
                    <RowDefinition Height="Auto" />
                    <RowDefinition Height="Auto" />
                    <RowDefinition Height="Auto" />
                    <RowDefinition Height="Auto" />
                    <RowDefinition Height="Auto" />
                    <RowDefinition Height="Auto" />
                    <RowDefinition Height="Auto" />
                </Grid.RowDefinitions>
                <Label Content="Customer Id:" Grid.Column="0" Grid.Row="0"  Margin="3" VerticalAlignment="Center" />
                <TextBox Grid.Column="1" Grid.Row="0"   Margin="3" Name="customerIdTextBox" Text="{Binding Path=CustomerId, Mode=TwoWay, ValidatesOnExceptions=true, NotifyOnValidationError=true}" VerticalAlignment="Center"  />
                <Label Content="Company Name:" Grid.Column="0" Grid.Row="1"  Margin="3" VerticalAlignment="Center" />
                <TextBox Grid.Column="1" Grid.Row="1"   Margin="3" Name="companyNameTextBox" Text="{Binding Path=CompanyName, Mode=TwoWay, ValidatesOnExceptions=true, NotifyOnValidationError=true}" VerticalAlignment="Center"  />
                <Label Content="Contact Name:" Grid.Column="0" Grid.Row="2"  Margin="3" VerticalAlignment="Center" />
                <TextBox Grid.Column="1" Grid.Row="2"   Margin="3" Name="contactNameTextBox" Text="{Binding Path=ContactName, Mode=TwoWay, ValidatesOnExceptions=true, NotifyOnValidationError=true}" VerticalAlignment="Center"  />
                <Label Content="Contact Title:" Grid.Column="0" Grid.Row="3"  Margin="3" VerticalAlignment="Center" />
                <TextBox Grid.Column="1" Grid.Row="3"   Margin="3" Name="contactTitleTextBox" Text="{Binding Path=ContactTitle, Mode=TwoWay, ValidatesOnExceptions=true, NotifyOnValidationError=true}" VerticalAlignment="Center"  />
                <Label Content="Address:" Grid.Column="0" Grid.Row="4" HorizontalAlignment="Left" Margin="3" VerticalAlignment="Center" />
                <TextBox Grid.Column="1" Grid.Row="4" Margin="3" Name="addressTextBox" Text="{Binding Path=Address, Mode=TwoWay, ValidatesOnExceptions=true, NotifyOnValidationError=true}" VerticalAlignment="Center" />
                <Label Content="City:" Grid.Column="0" Grid.Row="5"  Margin="3" VerticalAlignment="Center" />
                <TextBox Grid.Column="1" Grid.Row="5"   Margin="3" Name="cityTextBox" Text="{Binding Path=City, Mode=TwoWay, ValidatesOnExceptions=true, NotifyOnValidationError=true}" VerticalAlignment="Center"  />
                <Label Content="Postal Code:" Grid.Column="0" Grid.Row="6"  Margin="3" VerticalAlignment="Center" />
                <TextBox Grid.Column="1" Grid.Row="6"   Margin="3" Name="postalCodeTextBox" Text="{Binding Path=PostalCode, Mode=TwoWay, ValidatesOnExceptions=true, NotifyOnValidationError=true}" VerticalAlignment="Center"  />
                <Label Content="Region:" Grid.Column="0" Grid.Row="7"  Margin="3" VerticalAlignment="Center" />
                <TextBox Grid.Column="1" Grid.Row="7"   Margin="3" Name="regionTextBox" Text="{Binding Path=Region, Mode=TwoWay, ValidatesOnExceptions=true, NotifyOnValidationError=true}" VerticalAlignment="Center"  />
                <Label Content="Country:" Grid.Column="0" Grid.Row="8"  Margin="3" VerticalAlignment="Center" />
                <TextBox Grid.Column="1" Grid.Row="8"   Margin="3" Name="countryTextBox" Text="{Binding Path=Country, Mode=TwoWay, ValidatesOnExceptions=true, NotifyOnValidationError=true}" VerticalAlignment="Center"  />
                <Label Content="Phone:" Grid.Column="0" Grid.Row="9"  Margin="3" VerticalAlignment="Center" />
                <TextBox Grid.Column="1" Grid.Row="9"   Margin="3" Name="phoneTextBox" Text="{Binding Path=Phone, Mode=TwoWay, ValidatesOnExceptions=true, NotifyOnValidationError=true}" VerticalAlignment="Center"  />
                <Label Content="Fax:" Grid.Column="0" Grid.Row="10"  Margin="3" VerticalAlignment="Center" />
                <TextBox Grid.Column="1" Grid.Row="10"   Margin="3" Name="faxTextBox" Text="{Binding Path=Fax, Mode=TwoWay, ValidatesOnExceptions=true, NotifyOnValidationError=true}" VerticalAlignment="Center"  />
            </Grid>
        </Grid>
        <StackPanel Orientation="Horizontal" HorizontalAlignment="Right" Margin="5" Grid.Row="3">
            <Button Width="75" Height="25" Margin="5" Content="Add" Command="{Binding AddCommand}"/>
            <Button Width="75" Height="25" Margin="5" Content="Remove" Command="{Binding RemoveCommand}"/>
            <Button Width="75" Height="25" Margin="5" Content="Save" Command="{Binding SaveCommand}" />
        </StackPanel>
    </Grid>
</UserControl>
XML

There are some things to note, here:

  • There is no x:Class attribute in the UserControl
  • There is no code behind at all for the XAML. As this is a loose file, there should not be any cs file tied to it (and that’s a reason for not having the x:Class attribute)
  • I added the XAML files to the project, setting the Build Action to None and the Copy to Output Directory to Copy If Newer. This is an optional step, the files don’t need to be added to the project, they only need to be available on the executable directory at runtime

With this setting, you can run the application and get something like in the figure below:

You can see that everything works, both the data bindings and the commands. These files can be changed at will and they will be linked to the CustomersViewModel.

Conclusions

This kind of structure is very flexible and easy to use. Some uses I envision for it is to create different views for the same data, use layout customization for different clients, allow designers to be really free regarding to the design of the app, or create globalized apps (you can have different directories for each language, and each directory can have a translated view).

The next article will show subtler changes, when you only want to change the style of the controls.

The full code for the article is in https://github.com/bsonnino/DynamicXamlViews

4 thoughts on “Loading XAML Dynamically – Part 1 – Loading Views Dynamically”

  1. Mickey says:
    28 September 2018 at 14:35

    I know this is a late read, but have a question. I have a WPF app that does something similar to this but adds a new Tab with a View in it to a Tab List. Works great with one exception. One of my views had a 3rd party control that can only be called and init’d from the code behind. The View, View.xaml.cs and ViewModel all work together to make the 3rd party component work. If this View is used on application start, it works fine, but adding another during live use, I get a blank screen with the ViewModel name it. The codebehind never fires. Know of any solutions for this?

    Reply
    1. bsonnino says:
      22 November 2018 at 15:06

      You will have to have the code for calling and initializing the 3rd party control in your app (you cannot load it dinamically, unless you compile it at runtime, but that is another post :-)), then call this code when you load the view.

      You can see that the code for the buttons is already there, in the CustomersViewModel and it’s bound at runtime. You can add the code you need to the code behind of MainWindow, after loading the View:

      switch (viewIndex) { case 0: view = LoadView("masterdetail.xaml"); break; case 1: view = LoadView("detail.xaml"); InitializeThirdPartyControl(view); break; case 2: view = LoadView("master.xaml"); break; } MainContent.Content = view;

      Reply
  2. Max says:
    2 February 2019 at 01:55

    Thanks, very helpful !

    Reply
  3. Pingback: Loading Xaml Views and ViewModels Dynamically – Bruno Sonnino

Leave a Reply Cancel reply

Your email address will not be published. Required fields are marked *

  • May 2025
  • December 2024
  • October 2024
  • August 2024
  • July 2024
  • June 2024
  • November 2023
  • October 2023
  • August 2023
  • July 2023
  • June 2023
  • May 2023
  • November 2022
  • October 2022
  • September 2022
  • August 2022
  • June 2022
  • April 2022
  • March 2022
  • February 2022
  • January 2022
  • July 2021
  • June 2021
  • May 2021
  • April 2021
  • March 2021
  • February 2021
  • January 2021
  • December 2020
  • October 2020
  • September 2020
  • April 2020
  • March 2020
  • January 2020
  • November 2019
  • September 2019
  • August 2019
  • July 2019
  • June 2019
  • April 2019
  • March 2019
  • February 2019
  • January 2019
  • December 2018
  • November 2018
  • October 2018
  • September 2018
  • August 2018
  • July 2018
  • June 2018
  • May 2018
  • November 2017
  • October 2017
  • September 2017
  • August 2017
  • June 2017
  • May 2017
  • March 2017
  • February 2017
  • January 2017
  • December 2016
  • November 2016
  • October 2016
  • September 2016
  • August 2016
  • July 2016
  • June 2016
  • May 2016
  • April 2016
  • March 2016
  • February 2016
  • October 2015
  • August 2013
  • May 2013
  • February 2012
  • January 2012
  • April 2011
  • March 2011
  • December 2010
  • November 2009
  • June 2009
  • April 2009
  • March 2009
  • February 2009
  • January 2009
  • December 2008
  • November 2008
  • October 2008
  • July 2008
  • March 2008
  • February 2008
  • January 2008
  • December 2007
  • November 2007
  • October 2007
  • September 2007
  • August 2007
  • July 2007
  • Development
  • English
  • Português
  • Uncategorized
  • Windows

.NET AI Algorithms asp.NET Backup C# Debugging Delphi Dependency Injection Desktop Bridge Desktop icons Entity Framework JSON Linq Mef Minimal API MVVM NTFS Open Source OpenXML OzCode PowerShell Sensors Silverlight Source Code Generators sql server Surface Dial Testing Tools TypeScript UI Unit Testing UWP Visual Studio VS Code WCF WebView2 WinAppSDK Windows Windows 10 Windows Forms Windows Phone WPF XAML Zip

  • Entries RSS
  • Comments RSS
©2025 Bruno Sonnino | Design: Newspaperly WordPress Theme
Menu
  • Home
  • About