基于wpf制作一個可編程畫板
先上一張效果動圖
同樣老規(guī)矩,先上源碼地址: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數(shù)據(jù)
3.為了便于大家使用,寫了一個box工廠分配box的數(shù)據(jù)流向效果圖。
可編程模塊的實現(xiàn)原理
使用microsoft.codeanalysis.csharp.scripting對代碼進行編譯,生成assembly,然后對assembly反射獲得對象,對象內部固定有一個execute方法,每次掃描的時候執(zhí)行即可。
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.動態(tài)編譯的代碼的核心代碼如下:
public?static?assembly?generateassemblyfromcode(string?code,?out?string?message) { ????assembly?assembly?=?null; ????message?=?""; ????//?叢代碼中轉換表達式樹 ????syntaxtree?syntaxtree?=?csharpsyntaxtree.parsetext(code); ????//?隨機程序集名稱 ????string?assemblyname?=?path.getrandomfilename(); ????//?引用 ????//?創(chuàng)建編譯對象 ????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.獲得編譯后的程序集,以及執(zhí)行。
//?反射獲取程序集中?的類 type?type?=?assembly.gettypes().firstordefault(p?=>?p.fullname.startswith("aistudio.wpf"));???//assembly.gettype("aistudio.wpf.csharpscript.write"); //?創(chuàng)建該類的實例 object?obj?=?activator.createinstance(type); //?通過反射方式調用類中的方法。 var?result?=?type.invokemember("execute", ????bindingflags.default?|?bindingflags.invokemethod, ????null, ????obj, ????new?object[]?{?});
代碼編輯模塊的實現(xiàn)
選擇avalonedit控件,另外為了使用vs2019_dark的黑色皮膚,引用官方demo中的hl和texteditlib實現(xiàn)自定義換膚。
官方demo的換膚寫的超級復雜,看不懂,但是我們只要理解換膚的核心部分就是動態(tài)資源字典,因此我簡化下,改進后的核心換膚代碼如下:
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打印控制臺數(shù)據(jù)
///控制臺打印方法支持切換運行輸出方法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">日志方法委托(后三個參數(shù)為?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);
動態(tài)編譯模塊的輸入輸出自動生成
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的實現(xiàn)
1#.int整數(shù)模塊,界面定義一個textbox綁定int模塊的輸入管腳。 2#.box產(chǎn)生模塊,如果內部數(shù)組為空,那么按照輸入管腳的數(shù)量初始化一個容量為輸入整數(shù)數(shù)量的數(shù)組(隨機顏色與形狀),然后把數(shù)據(jù)放到輸出管腳,當數(shù)據(jù)被取走后,下一個數(shù)據(jù)再次放到輸出管腳。 3#.bool模塊,為false的時候按照顏色進行分配,為true的時候按照形狀進行分配。4#.box分配模塊,當輸入管腳為空的時候,2#模塊的輸出可以移動到4#的輸入管腳,移動時間為1s,移動完成后,清除2#模塊的輸出。同時把數(shù)據(jù)按照顏色或者形狀分配到輸出,同時把輸入管腳清除。 按照顏色分配時: (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#),按兩個容器中的數(shù)量比較反饋,均勻分配到這兩個收集器中。 9#,10#,11#,12#,13#,14#按照管腳取走數(shù)據(jù)即可。
以上就是基于wpf制作一個可編程畫板的詳細內容,更多關于wpf可編程畫板的資料請關注碩編程其它相關文章!