开发者

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

开发者 https://www.devze.com 2023-05-05 11:13 出处:网络 作者: 竹天笑
目录先上一张效果动图本次扩展的主要内容可编程模块的实现原理代码编辑模块的实现代码编辑模块的编译与测试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

精彩评论

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