基于WPF制作一个可编程画板
目录
- 先上一张效果动图
- 本次扩展的主要内容
- 可编程模块的实现原理
- 代码编辑模块的实现
- 代码编辑模块的编译与测试
- wpF打印控制台数据
- 动态编译模块的输入输出自动生成
先上一张效果动图
同样老规矩,先上源码地址:https://gitee.com/akwkevin/aistudio.-wpf.-diagram
简单使用,自定义一个text模块的代码如下
Code=@"usingSystem; namespaceAIStudio.Wpf.CSharpScript { publicclassWriter { publicstringStringValue{get;set;}=""WelcometoAIStudio.Wpf.Diagram""; publicstringExecute() { returnStringValue; } } }";
是不是很简单。
本次扩展的主要内容
1.可编程模块,使用C#语言。
2.控制台打印控件,可以打印程序中的Console.WriteLine数据
3.为了便于大家使用,写了一个Box工厂分配Box的数据流向效果图。
可编程模块的实现原理
使用Microsoft.CodeAnalysis.CSharp.Scripting对代码进行编译,生成Assembly,然后对Assembly反射获得对象,对象内部固定有一个Execute方法,每次扫描的时候执行即可。
1.编译使用的Using,必须添加引用集,为了省事,把整个程序的Reference都放入进行编译,获得引用的核心代码如下:
varreferences=AppDomapythonin.CurrentDomain.GetAssemblies().Where(p=>!p.IsDynamic&&!string.IsNullOrEmpty(p.Location)).Select(x=>MetadataReference.CreateFromFile(x.Location)).ToList(); //Costura.Fody压缩后,无Location,读取资源文件中的reference foreach(varassemblyEmbeddedinAppDomain.CurrentDomain.GetAssemblies().Where(p=>!p.IsDynamic&&string.IsNullOrEmpty(p.Location))) { using(varstream=Assembly.GetEntryAssembly().GetManifestResourceStream($"costura.{assemblyEmbedded.GetName().Name.ToLowerInvariant()}.dll.compressed")) { if(stream!=null) { using(varcompressStream=newDeflateStream(stream,CompressionMode.Decompress)) { varmemStream=newMemoryStream(); CopyTo(compressStream,memStream); memStream.Position=0; references.Add(MetadataReference.CreateFromStream(memStream)); } } } }
2.动态编译的代码的核心代码如下:
publicstaticAssemblyGenerateAssemblyFromCode(stringcode,outstringmessage) { Assemblyassembly=null; message=""; //丛代码中转换表达式树 SyntaxTreesyntaxTree=CSharpSyntaxTree.ParseText(code); //随机程序集名称 stringassemblyName=Path.GetRandomFileName(); //引用 //创建编译对象 CSharpCompilationcompilation=CSharpCompilation.Create(assemblyName,new[]{syntaxTree},References,newCSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)); using(varms=newMemoryStream()) { //将编译好的IL代码放入内存流 EmitResultresult=compilation.Emit(ms); //编译失败,提示 if(!result.Success) { IEnumerable开发者_Python学习<Diagnostic>failures=result.Diagnostics.Where(diagnostic=> diagnostic.IsWarningAsError|| diagnostic.Severity==DiagnosticSeverity.Error).ToList(); foreach(Diagnosticdiagnosticinfailures) { message+=$"{diagnostic.Id}:{diagnostic.GetMessage()}"; Console.WriteLine(message); } } else { //编译成功,从内存中加载编译好的程序集 ms.Seek(0,SeekOrigin.Begin); assembly=Assembly.Load(ms.ToArray()); } } returnassembly; }
3.获得编译后的程序集,以及执行。
//反射获取程序集中的类 Typetype=assembly.GetTypes().FirstOrDefault(p=>p.FullName.StartsWith("AIStudio.Wpf"));//assembly.GetType("AIStudio.Wpf.CSharpScript.Write"); //创建该类的实例 objectobj=Activator.CreateInstance(type); //通过反射方式调用类中的方法。 varresult=type.InvokeMember("Execute", BindingFlags.Default|BindingFlags.InvokeMethod, null, obj, newobject[]{});
代码编辑模块的实现
选择AvalonEdit控件,另外为了使用VS2019_Dark的黑色皮肤,引用官方Demo中的HL和TextEditlib实现自定义换肤。
官方Demo的换肤写的超级复杂,看不懂,但是我们只要理解换肤的核心部分就是动态资源字典,因此我简化下,改进后的核心换肤代码如下:
publicclassTextEditorThemeHelper { staticDictionary<string,ResourceDictionary>ThemeDictionary=newDictionary<string,ResourceDictionary>(); publicstaticList<string>Themes=newList<string>(){"Dark","Light","TrueBlue","VS2019_Dark"}; publicstaticstringCurrentTheme{get;set;} staticTextEditorThemeHelper() { varresource=newResourceDictionary{Source=newUri("/TextEditLib;component/Themes/LightBrushs.xaml",UriKind.RelativeOrAbsolute)}; ThemeDictionary.Add("Light",resource); resource=newResourceDictionary{Source=newUri("/TextEditLib;component/Themes/DarkBrushs.xaml",UriKind.RelativeOrAbsolute)}; ThemeDictionary.Add("Dark",resource); Application.Current.Resources.MergedDictionaries.Add(resource); } ///<summary> ///设置主题 ///</summary> ///<paramname="theme"></param> publicstaticvoidSetCurrentTheme(stringtheme) { OnAppThemeChanged(theme);//切换到VS2019_Dark CurrentTheme=theme; } ///<summary> ///Invokethismethodtoapplyachangeofthemetothecontentofthedocument ///(eg:Adjustthehighlightingcolorswhenchangingfrom"Dark"to"Light" ///WITHcurrenttextdocumentloaded.) ///</summary> internalstaticvoidOnAppThemeChanged(stringtheme) { ThemedHighlightingManager.Instance.SetCurrentTheme(theme); if(ThemeDictionary.ContainsKey(theme)) { foreach(varkeyinThemeDictionary[theme].Keys) { ApplyToDynamicResource(key,ThemeDictionary[theme][key]); } } //Doesthishighlightingdefinitionhaveanassociatedhighlightingtheme? elseif(ThemedHighlightingManager.Instance.CurrentTheme.HlTheme!=null) { //AhighlightingthemewithGlobalStyles? //Applythesestylestotheresourcekeysoftheeditor foreach(variteminThemedHighlightingManager.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(TextEdiaDCeNTktLib.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: thrownewSystem.ArgumentOutOfRangeException("GlobalStylenamed'{0}'isnotsupported.",item.TypeName); } } } } ///<summary> ///Re-defineanexisting<seealsocref="SolidColorBrush"/>andbackuptheoriginialcolor ///asitwasbeforetheapplicationofthecustomcoloring. ///</summary> ///<paramname="key"></param> ///<paramname="newColor"></param> privatestaticvoidApplyToDynamicResource(ComponentResourceKeykey,Color?newColor) { if(Application.Current.Resources[key]==null||newColor==null) return; //Re-coloringworkswithSolidColorBrushslinkedasDynamicResource if(Application.Current.Resources[key]isSolidColorBrush) { //backupDynResources.Add(resourceName); varnewColorBrush=newSolidColorBrush((Color)newColor); newColorBrush.Freeze(); Application.Current.Resources[key]=newColorBrush; } } privatestaticvoidApplyToDynamicResource(objectkey,objectnewValue) { 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,核心代码如下: publicclassConsoleWriter:TextWriter { privatereadonlyAction<string>_Write; privatereadonlyAction<string>_WriteLine; privatereadonlyAction<string,string,string,int>_WriteCallerInfo; publicConsoleWriter() { } ///<summary> ///Console输出重定向 ///</summary> ///<paramname="write">日志方法委托(针对于Write)</param> ///<paramname="writeLine">日志方法委托(针对于WriteLine)</param> publicConsoleWriter(Action<string>write,Action<string>writeLine,Action<string,string,string,int>writeCallerInfo) { _Write=write; _WriteLine=writeLine??write; _WriteCallerInfo=writeCallerInfo; } ///<summary> ///Console输出重定向 ///</summary> ///<paramname="write">日志方法委托(针对于Write)</param> ///<paramname="writeLine">日志方法委托(针对于WriteLine)</param> publicConsoleWriter(Action<string>write,Action<string>writeLine) { _Write=write; _WriteLine=writeLine; } ///<summary> ///Console输出重定向 ///</summary> ///<paramname="write">日志方法委托</param> publicConsoleWriter(Action<string>write) { _Write=write; _WriteLine=write; } ///<summary> ///Console输出重定向(带调用方信息) ///</summary> ///<paramname="write">日志方法委托(后三个参数为CallerFilePath、CallerMemberName、CallerLineNumber)</param> publicConsoleWriter(Action<string,string,string,int>write) { _WriteCallerInfo=write; } ///<summary> ///使用UTF-16避免不必要的编码转换 ///</summary> publicoverrideEncodingEncoding=>Encoding.Unicode; ///<summary> ///最低限度需要重写的方法 ///</summary> ///<paramname="value">消息</param> publicoverridevoidwrite(stringvalue) { if(_WriteCallerInfo!=null) { WriteWithCallerInfo(value); return; } _Write(value); } ///<summary> ///为提高效率直接处理一行的输出 ///</summary> ///<paramname="value">消息</param> publicoverridevoidWriteLine(stringvalue) { if(_WriteCallerInfo!=null) { WriteWithCallerInfo(value); return; } _WriteLine(value); } ///<summary> ///带调用方信息进行写消息 ///</summary> ///<paramname="value">消息</param> privatevoidWriteWithCallerInfo(stringvalue) { //3、System.Console.WriteLine->2、System.IO.TextWriter+SyncTextWriter.WriteLine->1、DotNet.Utilities.ConsoleHelper.ConsoleWriter.WriteLine->0、DotNet.Utilities.ConsoleHelper.ConsoleWriter.WriteWithCallerInfo varcallInfo=ClassHelper.GetMethodInfo(4); _WriteCallerInfo(value,callInfo?.FileName,callInfo?.MethodName,callInfo?.LineNumber??0); } publicoverridevoidClose() { varstandardOutput=newStreamWriter(Console.OpenStandardOutput()); standardOutput.AutoFlush=true; Console.SetOut(stanhttp://www.devze.comdardOutput); 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;} ,必须同名同属性。 核心代码如下:
publicstaticDictionary<string,List<PropertyInfo>>GetPropertyInfo(Typetype) { Dictionary<string,List<PropertyInfo>>puts=newDictionary<string,List<PropertyInfo>>() { {"Input",newList<PropertyInfo>()}, {"Output",newList<PropertyInfo>()}, {"Input_Output",newList<PropertyInfo>()}, {"Inner",newList<PropertyInfo>()} }; try { foreach(System.Reflection.PropertyInfoinfointype.GetProperties(BindingFlags.Public|BindingFlags.Instance)) { if(info.CanRead&&info.CanWrite) { if(info.SetMethod.IsPublic&&info.GetMethod.IsPublic) { puts["Input_Output"].Add(info); } elseif(info.SetMethod.IsPublic) { puts["Input"].Add(info); } elseif(info.GetMethod.IsPublic) { puts["Output"].Add(info); } } elseif(info.CanRead) { if(info.GetMethod.IsPublic) { puts["Output"].Add(info); } } } foreach(System.Reflection.PropertyInfoinfointype.GetProperties(BindingFlags.NonPublic|BindingFlags.Instance)) { if(info.CanRead) { puts["Inner"].Add(info); } } } catch(Exceptionex) { } returnjsputs; }
最后介绍一下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可编程画板的资料请关注我们其它相关文章!
精彩评论