Loading Xaml at Runtime

Loading XAML at runtime?

I think this is fairly simple with the XamlReader, give this a shot, didn't try it myself, but I think it should work.

https://learn.microsoft.com/en-us/archive/blogs/ashish/dynamically-loading-xaml

Is it possible to load XAML runtime in Avalonia

Install Avalonia.Markup.Xaml.Loader package and use AvaloniaRuntimeXamlLoader class.

Loading XAML at runtime using the MVVM pattern in WPF

I have a working solution now and I'd like to share it. Unfortunately I did not get rid of code-behind completely but it works as I expect it to. Here is how it works(simplified):

I have my simplified ViewModel:

public class MyViewModel : ViewModelBase
{
//This property implements INPC and triggers notification on Set
public string XamlViewData {get;set;}

public ViewModel()
{
GetXamlFormData();
}

//Gets the XAML Form from an external source (e.g. Database, File System)
public void GetXamlFormData()
{
//Set the Xaml String property
XamlViewData = //Logic to get XAML string from external source
}
}

Now my View:

<UserControl.Resources>
<ViewModel:MyViewModel x:Key="Model"></ViewModel:MyViewModel>
</UserControl.Resources>
<Grid DataContext="{StaticResource Model}">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition/>
</Grid.RowDefinitions>
<StackPanel>
<!-- This is the Grid used as a Place Holder to populate the dynamic content!-->
<Grid x:Name="content" Grid.Row="1" Margin="2"/>
<!-- Then create a Hidden TextBlock bound to my XamlString property. Right after binding happens I will trigger an event handled in the code-behind -->
<TextBlock Name="tb_XamlString" Text="{Binding Path=XamlViewData, Mode=TwoWay, UpdateSourceTrigger=LostFocus, NotifyOnValidationError=True, ValidatesOnDataErrors=True, ValidatesOnExceptions=True}" Visibility="Hidden" Loaded="tb_XamlString_Loaded" />
</StackPanel>
</Grid>

Basically I created a hidden TextBlock bound to my XAML String property in the ViewModel and I hooked its Loaded event to an event handler in the code behind of the View:

    private void tb_XamlString_Loaded(object sender, RoutedEventArgs routedEventArgs)
{
//First get the ViewModel from DataContext
MyViewModel vm = content.DataContext as MyViewModel;
FrameworkElement rootObject = XamlReader.Parse(vm.XamlViewData) as FrameworkElement;
//Add the XAML portion to the Grid content to render the XAML form dynamically!
content.Children.Add(rootObject);
}

This may not be the most elegant but gets the job done. Like some people say, in MVVM there are some cases like this where little code-behind code is needed. It doesn't hurt and also part of this solution still uses the V-VM Binding principles when using the VM to retrieve and populate the XamlString property and exposing it to the View. If we would like to Unit Test the XAML parsing and loading functionality we could delegate it to a separate class.

I hope someone finds this useful!

C# - load xaml file at runtime

A WPF application has by default, in the VS template, a StartupUri parameter:

<Application x:Class="WpfApplication2.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
StartupUri="MainWindow.xaml">
</Application>

The WPF framework will use this uri to instantiate a window class using XamlReader and show it. In your case - remove this StartUpUri from App.xaml and instantiate the class manually, so you can then hide it when you load your other window from xaml.

Now add this code to App.xaml.cs

 public partial class App : Application
{
Window mainWindow; // the instance of your main window

protected override void OnStartup(StartupEventArgs e)
{
mainWindow = new MainWindow();
mainWindow.Show();
}
}

To "replace" this Window with another window you:

  • hide this window using mainWindow.Hide();
  • read your personal.xaml with XamlReader which gives you the Window instance for the loaded Xaml.
  • assign it to mainWindow (if you want) and call Show() to display it.

Whether you want the instance of your apps "main window" hold a member of the App instance or not is certainly your choice.

In summary, the whole trick is:

  • hide the main window
  • load your window instance
  • show your window instance

Compile/Execute XAML during program runtime

This is how i solved the problem:

I have 3 files MainWindow.xaml, MainWindow.xaml.cs and wpftest.csproj.

MainWindow.xaml:

<Window x:Class="wpftest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Button Content="Button" HorizontalAlignment="Left" Margin="122,75,0,0" VerticalAlignment="Top" Width="75" Click="ButtonBase_OnClick"/>
</Grid>
</Window>

MainWindow.xaml.cs

namespace wpftest
{
using System.Windows;

/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}

private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
{
MessageBox.Show("Hello world", "does it work?");
}
}
}

wpftest.csproj
(Note: this file is very large and the most of the stuff is not necessary. You could create a much more simplified solution by following this guide: Walkthrough: Creating an MSBuild Project File from Scratch )

<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{E3FBAE41-AF7E-4C7E-A69E-ADAEAEE76FA2}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>wpftest</RootNamespace>
<AssemblyName>wpftest</AssemblyName>
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<ProjectTypeGuids>{60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Data" />
<Reference Include="System.Xml" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Core" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="System.Xaml">
<RequiredTargetFramework>4.0</RequiredTargetFramework>
</Reference>
<Reference Include="WindowsBase" />
<Reference Include="PresentationCore" />
<Reference Include="PresentationFramework" />
</ItemGroup>
<ItemGroup>
<Page Include="MainWindow.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</Page>
<Compile Include="MainWindow.xaml.cs">
<DependentUpon>MainWindow.xaml</DependentUpon>
<SubType>Code</SubType>
</Compile>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>

Solution:

The following code snippet will compile the code, created the dll file and use the created dll via reflection. This happens (all) during the application runtime.

Special thanks to doug for this stackoverflow question

    private void TestCase4_OnClick(object sender, RoutedEventArgs e)
{
var globalProperties = new Dictionary<string, string>();
var buildRequest = new BuildRequestData(@"C:\Users\jbu\wpftest\wpftest.csproj", globalProperties, null, new string[] { "Build" }, null);
var pc = new ProjectCollection();

var result = BuildManager.DefaultBuildManager.Build(new BuildParameters(pc), buildRequest);

Assembly assembly = Assembly.LoadFrom(@"C:\Users\jbu\wpftest\bin\Debug\wpftest.dll");
var instance = assembly.CreateInstance("wpftest.MainWindow") as Window;

if (instance != null)
{
instance.Show();
}
}


Related Topics



Leave a reply



Submit