有啥技术点

动态生成页面其实没什么技术点,原理就是通过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>