在运行时加载XAML?

首先是一些背景:我正在研究一个应用程序,我正在尝试遵循MVVM约定写它。 我想要做的一件事就是能够给应用程序提供不同的“皮肤”。 同一个应用程序,但为一个客户端显示一个“皮肤”,为另一个客户显示不同的“皮肤”。

所以我的问题是:
1.是否可以在运行时加载xaml文件并将其“分配”给我的应用程序?
2. xaml文件可以是驻留在不同文件夹中的外部文件吗?
3.应用程序能否轻松地切换到另一个xaml文件,或仅在启动时间?

那么我应该从哪里开始寻找这方面的信息呢? 哪些WPF方法(如果存在)处理这个function?

谢谢!

编辑:我想要做的“剥皮”的types不仅仅是改变我的控件的外观。 这个想法是有一个完全不同的用户界面。 不同的button,不同的布局。 有点像一个版本的应用程序将完全为专家function,另一个版本将简化初学者。

我认为XamlReader相当简单,给我一个机会,没有自己尝试,但我认为它应该工作。

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

正如Jakob Christensen指出的那样,您可以使用XamlReader.Load加载您想要的任何XAML。 这不仅适用于样式,而且也适用于UIElement 。 您只需加载XAML:

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

然后你可以将它设置为合适元素的内容,例如

 <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> 

你可以在grid添加rootElement

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

您自然也必须在代码隐藏中手动连接rootElement内部的任何事件。 作为一个例子,假设你的rootElement包含一个包含一堆PathCanvas ,你可以像这样分配PathMouseLeftButtonDown事件:

 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; } } 

我还没有尝试切换XAML文件,所以我不能说这是否真的工作。

您可以使用XamlReader.Load加载您想要的任何XAML。

如果在应用程序中设置了所有控件的样式并在应用程序资源字典中定义了这些样式,则可以使用XamlReader.Load在其他位置加载在XAML中定义的新样式,并用加载的XAMLreplace部分资源字典。 您的控件将相应地改变外观。

我已经在运行时加载了XAML,这里是一个简短的例子

 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>"; 

我做了简单的标记扩展,它加载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"); } } 

用法:

这个查找并加载MyFirstView.xaml文件

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

而这个填充整个UserControl(查找并加载MySecondView.xaml文件)

 <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}" /> 

看看http://www.codeproject.com/Articles/19782/Creating-a-Skinned-User-Interface-in-WPF-Josh Smith写了一篇关于如何在WPF中进行蒙皮的大文章。