开发者

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

目录
  • 先上一张效果动图
  • 本次扩展的主要内容
    • 可编程模块的实现原理
    • 代码编辑模块的实现
    • 代码编辑模块的编译与测试
    • 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实现自定义换肤。 

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

 官方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制作一个可编程画板

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

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可编程画板的资料请关注我们其它相关文章!

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新开发

开发排行榜