Loading XAML at runtime?

2019-01-03 04:46发布

First some background: I'm working on an application and I'm trying to follow MVVM conventions writing it. One thing I'd like to do is to be able to give the application different "skins" to my application. The same application, but show one "skin" for one client and a different "skin" for another.

And so my questions are:
1. Is it possible to load a xaml file at run time and "assign" it to my app?
2. Can the xaml file be an external file residing in a different folder?
3. Can the application switch to another xaml file easily, or only at startup time?

So where should I start looking at for information on this? Which WPF methods, if they exist, handle this functionality?

Thanks!

Edit: the type of "skinning" I'm wanting to do is more than just changing the look of my controls. The idea is having a completely different UI. Different buttons, different layouts. Kinda like how one version of the app would be fully featured for experts and another version would be simplified for beginners.

标签: c# wpf xaml
7条回答
家丑人穷心不美
2楼-- · 2019-01-03 05:20

As it's already mentioned in other answers, you can use XamlReader.Load.

If you are looking for a more straightforward example, here is an example showing how easily you can create a control from a string variable containing the XAML:

public T LoadXaml<T>(string xaml)
{
    using (var stringReader = new System.IO.StringReader(xaml))
    using (var xmlReader = System.Xml.XmlReader.Create(stringReader))
        return (T)System.Windows.Markup.XamlReader.Load(xmlReader);
}

And as the usage:

var xaml = "<TextBox xmlns='http://schemas.microsoft.com/winfx/2006/xaml/presentation\'>" +
            "Lorm ipsum dolor sit amet." +
            "</TextBox>";
var textBox = LoadXaml<System.Windows.Controls.TextBox>(xaml);
查看更多
一夜七次
3楼-- · 2019-01-03 05:31

Check out http://www.codeproject.com/Articles/19782/Creating-a-Skinned-User-Interface-in-WPF - Josh Smith wrote a great article on how to do skinning in WPF.

查看更多
手持菜刀,她持情操
4楼-- · 2019-01-03 05:32

I made simple markup extension, which loads xaml:

public class DynamicXamlLoader : MarkupExtension
{
    public DynamicXamlLoader() { }

    public DynamicXamlLoader(string xamlFileName)
    {
        XamlFileName = xamlFileName;
    }

    public string XamlFileName { get; set; }

    public override object ProvideValue(IServiceProvider serviceProvider)
    {
        var provideValue = serviceProvider.GetService(typeof(IProvideValueTarget)) as IProvideValueTarget;
        if (provideValue == null || provideValue.TargetObject == null) return null;

        // get target
        var targetObject = provideValue.TargetObject as UIElement;
        if (targetObject == null) return null;

        // get xaml file
        var xamlFile = new DirectoryInfo(Directory.GetCurrentDirectory())
            .GetFiles(XamlFileName ?? GenerateXamlName(targetObject), SearchOption.AllDirectories)
            .FirstOrDefault();

        if (xamlFile == null) return null;

        // load xaml
        using (var reader = new StreamReader(xamlFile.FullName))
            return XamlReader.Load(reader.BaseStream) as UIElement;
    }

    private static string GenerateXamlName(UIElement targetObject)
    {
        return string.Concat(targetObject.GetType().Name, ".xaml");
    }
}

Usage:

This find and load MyFirstView.xaml file

<ContentControl Content="{wpf:DynamicXamlLoader XamlFileName=MyFirstView.xaml}" />

And this fill whole UserControl (find and load MySecondView.xaml file)

<UserControl x:Class="MySecondView"
         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         Content="{wpf:DynamicXamlLoader}" />
查看更多
▲ chillily
5楼-- · 2019-01-03 05:35

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

http://blogs.msdn.com/ashish/archive/2007/08/14/dynamically-loading-xaml.aspx

查看更多
Summer. ? 凉城
6楼-- · 2019-01-03 05:35

As Jakob Christensen noted, you can load any XAML you want using XamlReader.Load. This doesn't apply only for styles, but UIElements as well. You just load the XAML like:

UIElement rootElement;
FileStream s = new FileStream(fileName, FileMode.Open);
rootElement = (UIElement)XamlReader.Load(s);
s.Close();

Then you can set it as the contents of the suitable element, e.g. for

<Window x:Class="MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="Foo Bar">
    <Grid x:Name="layoutGrid">
        <!-- any static elements you might have -->
    </Grid>
</Window>

you could add the rootElement in the grid with:

layoutGrid.Children.Add(rootElement);
layoutGrid.SetColumn(rootElement, COLUMN);
layoutGrid.SetRow(rootElement, ROW);

You'll naturally also have to connect any events for elements inside the rootElement manually in the code-behind. As an example, assuming your rootElement contains a Canvas with a bunch of Paths, you can assign the Paths' MouseLeftButtonDown event like this:

Canvas canvas = (Canvas)LogicalTreeHelper.FindLogicalNode(rootElement, "canvas1");
foreach (UIElement ui in LogicalTreeHelper.GetChildren(canvas)) {
    System.Windows.Shapes.Path path = ui as System.Windows.Shapes.Path;
    if (path != null) {
        path.MouseLeftButtonDown += this.LeftButtonDown;
    }
}

I've not tried switching XAML files on the fly, so I cannot say if that'll really work or not.

查看更多
▲ chillily
7楼-- · 2019-01-03 05:36

I have done loading XAML at runtime, here is a short example

Grid grd = new Grid();
var grdEncoding = new ASCIIEncoding();
var grdBytes = grdEncoding.GetBytes(myXAML);
grd = (Grid)XamlReader.Load(new MemoryStream(grdBytes));
Grid.SetColumn(grd, 0);
Grid.SetRow(grd, 0);
parentGrid.Children.Add(grd);

private String myXAML = @" <Grid xmlns='http://schemas.microsoft.com/winfx/2006/xaml/presentation' Margin='30 10 30 65' VerticalAlignment='Bottom'>" +
                    "<Label Content='Date: 1-Feb-2013' FontFamily='Arial' FontSize='12' Foreground='#666666' HorizontalAlignment='Left'/>" +
                    "<Label Content='4'  FontFamily='Arial' FontSize='12' Foreground='#666666' HorizontalAlignment='Center'/>" +
                    "<Label Content='Hello World'  FontFamily='Arial' FontSize='12' Foreground='#666666' HorizontalAlignment='Right'/>" +
                "</Grid>";
查看更多
登录 后发表回答