我的编程空间,编程开发者的网络收藏夹
学习永远不晚

基于WPF制作一个可编程画板

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

基于WPF制作一个可编程画板

先上一张效果动图

同样老规矩,先上源码地址:https://gitee.com/akwkevin/aistudio.-wpf.-diagram

简单使用,自定义一个text模块的代码如下

Code = @"using System;
namespace AIStudio.Wpf.CSharpScript
{
    public class Writer
    {   
        public string StringValue{ get; set;} = ""Welcome to AIStudio.Wpf.Diagram"";
        public string Execute()
        {
            return StringValue;
        }
    }
}";

是不是很简单。

本次扩展的主要内容

1.可编程模块,使用C#语言。

2.控制台打印控件,可以打印程序中的Console.WriteLine数据

3.为了便于大家使用,写了一个Box工厂分配Box的数据流向效果图。

可编程模块的实现原理

使用Microsoft.CodeAnalysis.CSharp.Scripting对代码进行编译,生成Assembly,然后对Assembly反射获得对象,对象内部固定有一个Execute方法,每次扫描的时候执行即可。

1.编译使用的Using,必须添加引用集,为了省事,把整个程序的Reference都放入进行编译,获得引用的核心代码如下:

var references = AppDomain.CurrentDomain.GetAssemblies().Where(p => !p.IsDynamic && !string.IsNullOrEmpty(p.Location)).Select(x => MetadataReference.CreateFromFile(x.Location)).ToList();
//Costura.Fody压缩后,无Location,读取资源文件中的reference
foreach (var assemblyEmbedded in AppDomain.CurrentDomain.GetAssemblies().Where(p => !p.IsDynamic && string.IsNullOrEmpty(p.Location)))
{
    using (var stream = Assembly.GetEntryAssembly().GetManifestResourceStream($"costura.{assemblyEmbedded.GetName().Name.ToLowerInvariant()}.dll.compressed"))
    {
        if (stream != null)
        {
            using (var compressStream = new DeflateStream(stream, CompressionMode.Decompress))
            {
                var memStream = new MemoryStream();
                CopyTo(compressStream, memStream);
                memStream.Position = 0;
                references.Add(MetadataReference.CreateFromStream(memStream));
            }
        }
    }
}

2.动态编译的代码的核心代码如下:

public static Assembly GenerateAssemblyFromCode(string code, out string message)
{
    Assembly assembly = null;
    message = "";
    // 丛代码中转换表达式树
    SyntaxTree syntaxTree = CSharpSyntaxTree.ParseText(code);
    // 随机程序集名称
    string assemblyName = Path.GetRandomFileName();
    // 引用
    // 创建编译对象
    CSharpCompilation compilation = CSharpCompilation.Create(assemblyName, new[] { syntaxTree }, References, new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary));
    using (var ms = new MemoryStream())
    {
        // 将编译好的IL代码放入内存流
        EmitResult result = compilation.Emit(ms);
        // 编译失败,提示
        if (!result.Success)
        {
            IEnumerable<Diagnostic> failures = result.Diagnostics.Where(diagnostic =>
                        diagnostic.IsWarningAsError ||
                        diagnostic.Severity == DiagnosticSeverity.Error).ToList();
            foreach (Diagnostic diagnostic in failures)
            {
                message += $"{diagnostic.Id}: {diagnostic.GetMessage()}";
                Console.WriteLine(message);
            }
        }
        else
        {
            // 编译成功,从内存中加载编译好的程序集
            ms.Seek(0, SeekOrigin.Begin);
            assembly = Assembly.Load(ms.ToArray());
        }
    }
    return assembly;
}

3.获得编译后的程序集,以及执行。

// 反射获取程序集中 的类
Type type = assembly.GetTypes().FirstOrDefault(p => p.FullName.StartsWith("AIStudio.Wpf"));   //assembly.GetType("AIStudio.Wpf.CSharpScript.Write");
// 创建该类的实例
object obj = Activator.CreateInstance(type);
// 通过反射方式调用类中的方法。
var result = type.InvokeMember("Execute",
    BindingFlags.Default | BindingFlags.InvokeMethod,
    null,
    obj,
    new object[] { });

代码编辑模块的实现

选择AvalonEdit控件,另外为了使用VS2019_Dark的黑色皮肤,引用官方Demo中的HL和TextEditlib实现自定义换肤。 

 官方Demo的换肤写的超级复杂,看不懂,但是我们只要理解换肤的核心部分就是动态资源字典,因此我简化下,改进后的核心换肤代码如下:

public class TextEditorThemeHelper
{
    static Dictionary<string, ResourceDictionary> ThemeDictionary = new Dictionary<string, ResourceDictionary>();
    public static List<string> Themes = new List<string>() { "Dark", "Light", "TrueBlue", "VS2019_Dark" };
    public static string CurrentTheme { get; set; }
    static TextEditorThemeHelper()
    {
        var resource = new ResourceDictionary { Source = new Uri("/TextEditLib;component/Themes/LightBrushs.xaml", UriKind.RelativeOrAbsolute) };
        ThemeDictionary.Add("Light", resource);
        resource = new ResourceDictionary { Source = new Uri("/TextEditLib;component/Themes/DarkBrushs.xaml", UriKind.RelativeOrAbsolute) };
        ThemeDictionary.Add("Dark", resource);
        Application.Current.Resources.MergedDictionaries.Add(resource);
    }
    /// <summary>
    /// 设置主题
    /// </summary>
    /// <param name="theme"></param>
    public static void SetCurrentTheme(string theme)
    {
        OnAppThemeChanged(theme);//切换到VS2019_Dark
        CurrentTheme = theme;
    }
    /// <summary>
    /// Invoke this method to apply a change of theme to the content of the document
    /// (eg: Adjust the highlighting colors when changing from "Dark" to "Light"
    ///      WITH current text document loaded.)
    /// </summary>
    internal static void OnAppThemeChanged(string theme)
    {
        ThemedHighlightingManager.Instance.SetCurrentTheme(theme);
        if (ThemeDictionary.ContainsKey(theme))
        {
            foreach (var key in ThemeDictionary[theme].Keys)
            {
                ApplyToDynamicResource(key, ThemeDictionary[theme][key]);
            }
        }
        // Does this highlighting definition have an associated highlighting theme?
        else if (ThemedHighlightingManager.Instance.CurrentTheme.HlTheme != null)
        {
            // A highlighting theme with GlobalStyles?
            // Apply these styles to the resource keys of the editor
            foreach (var item in ThemedHighlightingManager.Instance.CurrentTheme.HlTheme.GlobalStyles)
            {
                switch (item.TypeName)
                {
                    case "DefaultStyle":
                        ApplyToDynamicResource(TextEditLib.Themes.ResourceKeys.EditorBackground, item.backgroundcolor);
                        ApplyToDynamicResource(TextEditLib.Themes.ResourceKeys.EditorForeground, item.foregroundcolor);
                        break;
                    case "CurrentLineBackground":
                        ApplyToDynamicResource(TextEditLib.Themes.ResourceKeys.EditorCurrentLineBackgroundBrushKey, item.backgroundcolor);
                        ApplyToDynamicResource(TextEditLib.Themes.ResourceKeys.EditorCurrentLineBorderBrushKey, item.bordercolor);
                        break;
                    case "LineNumbersForeground":
                        ApplyToDynamicResource(TextEditLib.Themes.ResourceKeys.EditorLineNumbersForeground, item.foregroundcolor);
                        break;
                    case "Selection":
                        ApplyToDynamicResource(TextEditLib.Themes.ResourceKeys.EditorSelectionBrush, item.backgroundcolor);
                        ApplyToDynamicResource(TextEditLib.Themes.ResourceKeys.EditorSelectionBorder, item.bordercolor);
                        break;
                    case "Hyperlink":
                        ApplyToDynamicResource(TextEditLib.Themes.ResourceKeys.EditorLinkTextBackgroundBrush, item.backgroundcolor);
                        ApplyToDynamicResource(TextEditLib.Themes.ResourceKeys.EditorLinkTextForegroundBrush, item.foregroundcolor);
                        break;
                    case "NonPrintableCharacter":
                        ApplyToDynamicResource(TextEditLib.Themes.ResourceKeys.EditorNonPrintableCharacterBrush, item.foregroundcolor);
                        break;
                    default:
                        throw new System.ArgumentOutOfRangeException("GlobalStyle named '{0}' is not supported.", item.TypeName);
                }
            }
        }
    }
    /// <summary>
    /// Re-define an existing <seealso cref="SolidColorBrush"/> and backup the originial color
    /// as it was before the application of the custom coloring.
    /// </summary>
    /// <param name="key"></param>
    /// <param name="newColor"></param>
    private static void ApplyToDynamicResource(ComponentResourceKey key, Color? newColor)
    {
        if (Application.Current.Resources[key] == null || newColor == null)
            return;
        // Re-coloring works with SolidColorBrushs linked as DynamicResource
        if (Application.Current.Resources[key] is SolidColorBrush)
        {
            //backupDynResources.Add(resourceName);
            var newColorBrush = new SolidColorBrush((Color)newColor);
            newColorBrush.Freeze();
            Application.Current.Resources[key] = newColorBrush;
        }
    }
    private static void ApplyToDynamicResource(object key, object newValue)
    {
        if (Application.Current.Resources[key] == null || newValue == null)
            return;
        Application.Current.Resources[key] = newValue;
    }
}

使用方法:

TextEditorThemeHelper.SetCurrentTheme("VS2019_Dark");

或者 TextEditorThemeHelper.SetCurrentTheme("TrueBlue");

或者 TextEditorThemeHelper.SetCurrentTheme("Dark");

或者 TextEditorThemeHelper.SetCurrentTheme("Light");

是不是超级简单。

代码编辑模块的编译与测试

WPF打印控制台数据

///控制台打印方法支持切换运行输出方法Console.SetOut,核心代码如下:
public class ConsoleWriter : TextWriter
{
    private readonly Action<string> _Write;
    private readonly Action<string> _WriteLine;
    private readonly Action<string, string, string, int> _WriteCallerInfo;
    public ConsoleWriter()
    {
    }
    /// <summary>
    /// Console 输出重定向
    /// </summary>
    /// <param name="write">日志方法委托(针对于 Write)</param>
    /// <param name="writeLine">日志方法委托(针对于 WriteLine)</param>
    public ConsoleWriter(Action<string> write, Action<string> writeLine, Action<string, string, string, int> writeCallerInfo)
    {
        _Write = write;
        _WriteLine = writeLine?? write;
        _WriteCallerInfo = writeCallerInfo;
    }
    /// <summary>
    /// Console 输出重定向
    /// </summary>
    /// <param name="write">日志方法委托(针对于 Write)</param>
    /// <param name="writeLine">日志方法委托(针对于 WriteLine)</param>
    public ConsoleWriter(Action<string> write, Action<string> writeLine)
    {
        _Write = write;
        _WriteLine = writeLine;
    }
    /// <summary>
    /// Console 输出重定向
    /// </summary>
    /// <param name="write">日志方法委托</param>
    public ConsoleWriter(Action<string> write)
    {
        _Write = write;
        _WriteLine = write;
    }
    /// <summary>
    /// Console 输出重定向(带调用方信息)
    /// </summary>
    /// <param name="write">日志方法委托(后三个参数为 CallerFilePath、CallerMemberName、CallerLineNumber)</param>
    public ConsoleWriter(Action<string, string, string, int> write)
    {
        _WriteCallerInfo = write;
    }
    /// <summary>
    /// 使用 UTF-16 避免不必要的编码转换
    /// </summary>
    public override Encoding Encoding => Encoding.Unicode;
    /// <summary>
    /// 最低限度需要重写的方法
    /// </summary>
    /// <param name="value">消息</param>
    public override void Write(string value)
    {
        if (_WriteCallerInfo != null)
        {
            WriteWithCallerInfo(value);
            return;
        }
        _Write(value);
    }
    /// <summary>
    /// 为提高效率直接处理一行的输出
    /// </summary>
    /// <param name="value">消息</param>
    public override void WriteLine(string value)
    {
        if (_WriteCallerInfo != null)
        {
            WriteWithCallerInfo(value);
            return;
        }
        _WriteLine(value);
    }
    /// <summary>
    /// 带调用方信息进行写消息
    /// </summary>
    /// <param name="value">消息</param>
    private void WriteWithCallerInfo(string value)
    {
        //3、System.Console.WriteLine -> 2、System.IO.TextWriter + SyncTextWriter.WriteLine -> 1、DotNet.Utilities.ConsoleHelper.ConsoleWriter.WriteLine -> 0、DotNet.Utilities.ConsoleHelper.ConsoleWriter.WriteWithCallerInfo
        var callInfo = ClassHelper.GetMethodInfo(4);
        _WriteCallerInfo(value, callInfo?.FileName, callInfo?.MethodName, callInfo?.LineNumber ?? 0);
    }
    public override void Close()
    {
        var standardOutput = new StreamWriter(Console.OpenStandardOutput());
        standardOutput.AutoFlush = true;
        Console.SetOut(standardOutput);
        base.Close();
    }
}

使用:

ConsoleWriter ConsoleWriter = new ConsoleWriter(_write, _writeLine);

Console.SetOut(ConsoleWriter);

动态编译模块的输入输出自动生成

1.输入输出模块:public string Value{ get; set;}

2.输入模块:public string Value{private get; set;}

3.输出模块:public string Value{get;private set;}

4.与外部交互模块:private string Value{ get; set;} ,必须同名同属性。 核心代码如下:

public static Dictionary<string, List<PropertyInfo>> GetPropertyInfo(Type type)
{
    Dictionary<string, List<PropertyInfo>> puts = new Dictionary<string, List<PropertyInfo>>()
    {
        {"Input", new List<PropertyInfo>() },
        {"Output", new List<PropertyInfo>() },
        {"Input_Output", new List<PropertyInfo>() },
        {"Inner", new List<PropertyInfo>() }
    };
    try
    {
        foreach (System.Reflection.PropertyInfo info in type.GetProperties(BindingFlags.Public | BindingFlags.Instance))
        {
            if (info.CanRead && info.CanWrite)
            {
                if (info.SetMethod.IsPublic && info.GetMethod.IsPublic)
                {
                    puts["Input_Output"].Add(info);
                }
                else if (info.SetMethod.IsPublic)
                {
                    puts["Input"].Add(info);
                }
                else if (info.GetMethod.IsPublic)
                {
                    puts["Output"].Add(info);
                }
            }
            else if (info.CanRead)
            {
                if (info.GetMethod.IsPublic)
                {
                    puts["Output"].Add(info);
                }
            }
        }
        foreach (System.Reflection.PropertyInfo info in type.GetProperties(BindingFlags.NonPublic | BindingFlags.Instance))
        {
            if (info.CanRead)
            {
                puts["Inner"].Add(info);
            }
        }
    }
    catch (Exception ex)
    {
    }
    return puts;
}

最后介绍一下Demo的实现

1#.Int整数模块,界面定义一个TextBox绑定Int模块的输入管脚。 2#.Box产生模块,如果内部数组为空,那么按照输入管脚的数量初始化一个容量为输入整数数量的数组(随机颜色与形状),然后把数据放到输出管脚,当数据被取走后,下一个数据再次放到输出管脚。 3#.Bool模块,为false的时候按照颜色进行分配,为true的时候按照形状进行分配。4#.Box分配模块,当输入管脚为空的时候,2#模块的输出可以移动到4#的输入管脚,移动时间为1s,移动完成后,清除2#模块的输出。同时把数据按照颜色或者形状分配到输出,同时把输入管脚清除。 按照颜色分配时: (1.如果颜色为红色,那么输出到1号 (2.如果颜色为橙色,那么输出到2号 (3.如果颜色为黄色,那么输出到3号 (4.如果颜色为绿色,那么输出到4号 (5.如果颜色为青色,那么输出到5号 (6.如果颜色为蓝色,那么输出到6号 (7.如果颜色为紫色,那么输出到7号 按照形状分配时: (1.如果形状为圆形,那么输出到1号 (2.如果形状为三角形,那么输出到2号 (3.如果形状为方形,那么输出到3号 (4.如果形状为菱形,那么输出到4号 (5.如果形状为梯形,那么输出到5号 (6.如果形状为五角星,那么输出到6号 (7.如果形状为六边形,那么输出到7号 6#.有两个红色|圆形收集器(7#,8#),按两个容器中的数量比较反馈,均匀分配到这两个收集器中。 9#,10#,11#,12#,13#,14#按照管脚取走数据即可。

以上就是基于WPF制作一个可编程画板的详细内容,更多关于WPF可编程画板的资料请关注编程网其它相关文章!

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

基于WPF制作一个可编程画板

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

基于WPF制作一个可编程画板

这篇文章主要为大家详细介绍了如何基于WPF制作一个可编程画板,文中的示例代码讲解详细,对我们学习或工作有一定帮助,感兴趣的小伙伴可以了解一下
2023-05-18

基于WPF绘制一个点赞大拇指动画

这篇文章主要为大家详细介绍了WPF实现绘制一个点赞大拇指动画,文中的示例代码讲解详细,对我们学习或工作有一定帮助,感兴趣的小伙伴可以了解一下
2023-02-13

基于Flutter怎么制作一个火箭发射动画

本文小编为大家详细介绍“基于Flutter怎么制作一个火箭发射动画”,内容详细,步骤清晰,细节处理妥当,希望这篇“基于Flutter怎么制作一个火箭发射动画”文章能帮助大家解决疑惑,下面跟着小编的思路慢慢深入,一起来学习新知识吧。Anima
2023-06-29

基于Flutter怎么制作一个心碎动画特效

这篇文章主要介绍了基于Flutter怎么制作一个心碎动画特效的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇基于Flutter怎么制作一个心碎动画特效文章都会有所收获,下面我们一起来看看吧。效果图先上:实现步骤1
2023-06-30

基于JavaSpringBoot+uniapp制作一个记账小程序

你不理财,财不理你,制作一个记账小程序对自己的收入/支出明细进行管理,守护好自己的钱袋子。 一、小程序 1.1 项目创建 1.2 首页 1.3 收支报表页
2023-08-16

基于JavaSpringBoot+uniapp制作一个打卡小程序

生活中打卡的场景无处不在,上班打卡、景点打卡、活动打卡、课堂考勤打卡、在CSDN的APP上也有签到打卡。 一、小程序 1. 创建小程序 2. 地图页
2023-08-24

基于Java+SpringBoot制作一个论坛小程序

制作一个论坛互动平台,让兴趣志同道合者用户聚集在这里交流话题、展示自我、结交朋友。 一、小程序 1.1 项目创建 1.2 首页 1.3 论坛板块页 1.
2023-08-16

基于Java+SpringBoot制作一个宿舍报修小程序

制作一个宿舍报修小程序,让学生实现快速报修,将流程进行精简, 便于管理部门有效响应。 微信小程序实战开发专栏 一、小程序 1.1 项目创建 1.2 首页
2023-08-16

基于Java+SpringBoot制作一个旅游攻略小程序

憋了几年好不容易解封准备出去散散心,但看着大江南北这么多景点是不是有点让你选择强迫症呢?那就先制作一个旅游攻略小程序看看驴友们的分享吧。 来源地址:https
2023-08-30

基于小程序制作一个ChatGPT聊天机器人

在AI技术日新月异的浪潮中,将ChatGPT与实战开发相结合,制作一个随身携带的聊天机器人,紧贴前沿的同时稳固基础。 一、前言 1.1、什么是ChatGPT 1.2、什么是文本完成
2023-08-30

基于Java+SpringBoot制作一个智能用电小程序

在当今快节奏的生活中,高效利用能源变得越来越重要。制作一个智能用电小程序,旨在帮助您更智能地管理家庭电器的用电,从而提升能源利用效率,助您掌握用电情况,降低能耗成本,实现绿色低碳生活。 目录
2023-08-16

基于Java+SpringBoot制作一个校园圈子小程序

制作一个校园圈子小程序,有效连接信息供需双方,用户可获取和发布需求信息、参与互动交流,适用于同校、同城、社区交流、客户互动、同学/老师注册发布信息等场景。 微信小程序实战开发专栏 一、小
2023-08-16

基于Java+SpringBoot制作一个学生公寓管理小程序

制作一个学生公寓管理小程序,旨在优化和简化学生公寓的日常管理工作。该系统涵盖了各种功能模块,以满足学生住宿的需求,同时提供方便、高效的管理方式,该系统包含用户管理、卫生评比、来访登记、宿舍报修等模块。
2023-08-16

基于Java+SpringBoot制作一个社区宠物登记小程序

制作一个社区宠物登记小程序,帮助社区居民登记和管理他们的宠物信息,以便更好地跟踪和维护社区中的宠物状况,通过社区宠物信息登记系统,社区居民可以更好地管理自己的宠物信息,并与其他宠物所有者共享资源和信息。这将有助于提高社区宠物的福利和安全
2023-08-16

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录