有啥技术点
动态生成页面其实没什么技术点,原理就是通过C#代码往页面中添加各种布局、控件、事件等等
生成动态链接库呢就是把代码打包成dll文件,这个微软官方有类库能够实现,我们需要做的就是把系统所需要的依赖库加载进去。
废话不多说,开始
动态生成页面
我们创建一个WPF页面的时候是不是会有个 xaml文件和cs文件 ,正常我们写一个页面都是在xaml中写的,cs文件写一些数据绑定等,当然也有mvvm模式可以不在cs文件中写,但是 渲染页面 的代码是在cs里,所以我们想要 动态生成页面 就得在这个cs文件中下手,我们先创建一个空白的页面看看
可以看到一个 页面的组成和结构 是这样的,页面的渲染是通过 InitializeComponent 方法实现的,这个是官方写好的通过xaml文件去渲染,那我们反其道而行之,我们 不要xaml文件 了,直接 通过cs文件去渲染页面 。那具体咋实现呢?
我们再单独创建cs类文件,取名UserControl2
现在整个工程是这样的,确定是cs文件哈
首先我们继承
UserControl
类,如果是页面的话就是
Page
,如果是窗体的话就是
Windows
,这里就拿用户控件举例子了,需要添加引用
System.Windows.Controls
这是构建wpf页面所必须的类库
接下来就写初始化组件的方法了
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Controls;
using System.Windows;
using System.Windows.Data;
using System.Windows.Markup;
namespace WPFApp1
public class UserControl2 : UserControl
TextBox txt;
private int count=0;
public UserControl2()
InitializeComponent();
void InitializeComponent()
StackPanel panel = new StackPanel();
IAddChild container = panel;
txt = new TextBox();
txt.Text = "测试文本框";
container.AddChild(txt);
Button btn = new Button();
btn.Content = "测试按钮";
btn.Click+=onButtonClick;
container.AddChild(btn);
this.AddChild(panel);
void onButtonClick(object sender, RoutedEventArgs e)
txt.Text = $"测试按钮事件:{count++}";
现在在窗体里面引用一下
<Window x:Class="WPFApp1.MainWindow"
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"
xmlns:local="clr-namespace:WPFApp1"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<StackPanel>
<local:UserControl2></local:UserControl2>
</StackPanel>
</Grid>
</Window>
现在就能正常看到效果了
也能够添加其他控件,例如DataGrid
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Controls;
using System.Windows;
using System.Windows.Data;
using System.Windows.Markup;
namespace WPFApp1
public class UserControl2 : UserControl
TextBox txt;
private int count=0;
private List<User> userList = new List<User>()
new User(){ Id=1,Name="张三",Age=15 },
new User(){ Id=2,Name="李四",Age=16 },
new User(){ Id=3,Name="王五",Age=17 },
new User(){ Id=4,Name="赵六",Age=18 },
new User(){ Id=5,Name="田七",Age=19 },
public UserControl2()
InitializeComponent();
void InitializeComponent()
StackPanel panel = new StackPanel();
IAddChild container = panel;
txt = new TextBox();
txt.Text = "测试文本框";
container.AddChild(txt);
Button btn = new Button();
btn.Content = "测试按钮";
btn.Click+=onButtonClick;
container.AddChild(btn);
//创建一个DataGrid
DataGrid datagrid = new DataGrid() { Name = "list" };
datagrid.AutoGenerateColumns = false;//使用这一句禁止创建新列,不然的话会将绑定列重新创建一遍
datagrid.Columns.Add(new DataGridTextColumn() { Header = "编号", Width = 150, Binding = new Binding("Id") });
datagrid.Columns.Add(new DataGridTextColumn() { Header = "姓名", Width = 150, Binding = new Binding("Name") });
datagrid.Columns.Add(new DataGridTextColumn() { Header = "年龄", Width = 150, Binding = new Binding("Age") });
datagrid.ItemsSource = userList;
container.AddChild(datagrid);
this.AddChild(panel);
void onButtonClick(object sender, RoutedEventArgs e)
txt.Text = $"测试按钮事件:{count++}";
public class User
public int Id { get; set; }//编号
public string Name { get; set; }//姓名
public int Age { get; set; }//年龄
动态编译动态链接库
什么意思呢,动态链接库也就是dll文件,而想把代码生成dll,这个都是我们编译器帮我去实现的,但是现在我们的需求是要在系统运行时去生成dll,那就不能通过编译器去编译了,但是也不用担心,微软官方贴心的给我们提供了类库
System.CodeDom
Microsoft.CodeAnalysis
这里有两种方式可以实现,分别是这两个包,可以根据.NET版本和使用习惯来选择,下面进行一一举例
这个包是.NET Core推出后才有的包。他的使用方式跟CodeDom也类似
直接上代码
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.Emit;
using Microsoft.CSharp;
using System;
using System.CodeDom.Compiler;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Text;
namespace ConsoleApp1
class Program
static void Main(string[] args)
List<string> codes = new List<string>();
codes.Add(GetContentByTxt("D:/SourceCode/User.txt"));
codes.Add(GetContentByTxt("D:/SourceCode/UserControl2.txt"));
//错误信息记录
StringBuilder str = new StringBuilder();
var result = CodeDomGenerateDynamic(codes, "TestDemo");
foreach (var item in result.Errors)
str.AppendLine("CodeDom方法报错:" + item.ToString());
var result2 = CodeAnalysisGenerateDynamic(codes, "TestDemo2");
if (!result2.Success)
foreach (var item in result2.Diagnostics)
str.AppendLine("CodeAnalysis方法报错:" + item.ToString());
Console.WriteLine(str.ToString());
public static string GetContentByTxt(string path)
string conStr = "";
FileStream fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read);
StreamReader reader = new StreamReader(fs);
conStr = reader.ReadToEnd();
catch (Exception)
conStr = "";
return conStr;
public static CompilerResults CodeDomGenerateDynamic(List<string> code, string DynamicLinkLibraryName)
CSharpCodeProvider complier = new CSharpCodeProvider();
//设置编译参数
CompilerParameters paras = new CompilerParameters();
//引入依赖项
//paras.ReferencedAssemblies.Add("System.dll");
paras.ReferencedAssemblies.Add("D:/SourceDLL/System.dll");
paras.ReferencedAssemblies.Add("D:/SourceDLL/System.Windows.dll");
paras.ReferencedAssemblies.Add("D:/SourceDLL/PresentationFramework.dll");
paras.ReferencedAssemblies.Add("D:/SourceDLL/PresentationCore.dll");
paras.ReferencedAssemblies.Add("D:/SourceDLL/WindowsBase.dll");
paras.ReferencedAssemblies.Add("D:/SourceDLL/System.Xaml.dll");
paras.ReferencedAssemblies.Add("D:/SourceDLL/System.Collections.dll");
paras.GenerateInMemory = false;
//是否生成可执行文件
paras.GenerateExecutable = false;
string path = Path.Combine("D:/", "DynamicLinkLibrary/" + DynamicLinkLibraryName + ".dll");
paras.OutputAssembly = path;
//编译代码
CompilerResults result = complier.CompileAssemblyFromSource(paras, code.ToArray());
return result;
public static EmitResult CodeAnalysisGenerateDynamic(List<string> codes, string DynamicLinkLibraryName)
List<MetadataReference> References = new List<MetadataReference>();
//引入依赖项
References.Add(MetadataReference.CreateFromFile(typeof(object).GetTypeInfo().Assembly.Location));
References.Add(MetadataReference.CreateFromFile(Assembly.Load(new AssemblyName("netstandard")).Location));
References.Add(MetadataReference.CreateFromFile("D:/SourceDLL/System.dll"));
References.Add(MetadataReference.CreateFromFile("D:/SourceDLL/System.Windows.dll"));
References.Add(MetadataReference.CreateFromFile("D:/SourceDLL/PresentationFramework.dll"));
References.Add(MetadataReference.CreateFromFile("D:/SourceDLL/PresentationCore.dll"));
References.Add(MetadataReference.CreateFromFile("D:/SourceDLL/WindowsBase.dll"));
References.Add(MetadataReference.CreateFromFile("D:/SourceDLL/System.Xaml.dll"));
References.Add(MetadataReference.CreateFromFile("D:/SourceDLL/System.Collections.dll"));
var compilation = CSharpCompilation.Create(DynamicLinkLibraryName)
.WithOptions(
new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary))
.AddReferences(References.ToArray());
foreach (string code in codes)
var tree = SyntaxFactory.ParseSyntaxTree(code);
compilation = compilation.AddSyntaxTrees(tree);
string path = Path.Combine("D:/", "DynamicLinkLibrary/" + DynamicLinkLibraryName + ".dll");
//编译代码
EmitResult compilationResult = compilation.Emit(path);
return compilationResult;
这里需要注意的是,因为 .NET Framework 版本可能不一样,需要去把原来的dll复制出来,以区分管理,如果确定版本一致的话直接使用名称即可获取到当前启动项目的 .NET Framework对应的版本 ,但是有的时候也会出现 找不到对应dll 的情况,所以我建议还是去复制出来,地址就是下图这么查看的
那我们来看看生成出来的动态链接库能不能正常使用,如果生成的dll太小了,比如 2kb 左右,那就说明是有问题的,要检查下代码看哪里出错了,CodeAnalysis 库生成的动态链接库稍微小一点
引用下看看
<Window x:Class="WPFApp1.MainWindow"
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"
xmlns:local="clr-namespace:WPFApp1" xmlns:demo="clr-namespace:Demo;assembly=TestDemo"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<StackPanel>
<!--<local:UserControl2></local:UserControl2>-->
<demo:UserControl2></demo:UserControl2>
</StackPanel>
</Grid>
</Window>