# aistudio-wpf-python-release **Repository Path**: akwkevin/aistudio-wpf-python-release ## Basic Information - **Project Name**: aistudio-wpf-python-release - **Description**: No description available - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2025-10-25 - **Last Updated**: 2025-10-25 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # aistudio-wpf-python-release(Python脚本) [相同功能的C#脚本版本](https://gitee.com/akwkevin/aistudio-wpf-script-release) #### 介绍 请运行根目录下的AIStudio.Wpf.Python.Demo.exe,查看效果(需要安装python313,获取源码后,可以自己改Python版本)。 #### AIStudio框架汇总:[https://gitee.com/akwkevin/aistudio.-introduce](https://gitee.com/akwkevin/aistudio.-introduce) #### 效果图 ![输入图片说明](https://gitee.com/akwkevin/aistudio.-wpf.-diagram/raw/master/Images/55.gif) ### 1.简单使用,自定义一个text模块的代码如下: ![输入图片说明](image.png) 是不是很简单。 ### 2.本次扩展的主要内容 【1】.可编程模块,使用Python语言。 【2】.控制台打印控件,可以打印程序中的print数据 【3】.为了便于大家使用,写了一个Box工厂分配Box的数据流向效果图。 ### 3.可编程模块的实现原理 使用pythonnet。 1.初始化的的核心代码如下: ``` public static void Init() { Runtime.PythonDLL = "python313.dll"; PythonEngine.Initialize(); _ts = PythonEngine.BeginAllowThreads(); using (Py.GIL()) // 获取全局解释器锁 { // 自定义 stdout.write() 方法 string code = @" import sys class CustomStdOut: def write(self, text): if hasattr(sys, 'csharp_stdout_write'): sys.csharp_stdout_write(text) else: sys.__stdout__.write(text) def flush(self): pass sys.stdout = CustomStdOut() "; PythonEngine.Exec(code); // 注册 C# 的回调方法到 Python 的 sys 模块中 dynamic sys = Py.Import("sys"); sys.csharp_stdout_write = new Action(text => { if (!string.IsNullOrEmpty(text) && text != "\n") { Console.Write(text); // 所有 print 都会来到这里 } }); } } ``` 2.动态编译的代码的核心代码如下: ``` public static PyObject CompileCode(string code, out string message) { PyObject pythonCode = null; try { message = ""; // 2. 执行Python代码(可以使用这些变量) pythonCode = PythonEngine.Compile(code); } catch(Exception ex) { message = ex.Message; } return pythonCode; } ``` ### 3.代码编辑模块的实现 选择AvalonEdit控件,另外为了使用VS2019_Dark的黑色皮肤,引用官方Demo中的HL和TextEditlib实现自定义换肤。 ![输入图片说明](https://gitee.com/akwkevin/aistudio.-wpf.-diagram/raw/master/Images/editor.png) 官方Demo的换肤写的超级复杂,看不懂,但是我们只要理解换肤的核心部分就是动态资源字典,因此我简化下,改进后的核心换肤代码如下: ``` public class TextEditorThemeHelper { static Dictionary ThemeDictionary = new Dictionary(); public static List Themes = new List() { "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); } /// /// 设置主题 /// /// public static void SetCurrentTheme(string theme) { OnAppThemeChanged(theme);//切换到VS2019_Dark CurrentTheme = theme; } /// /// 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.) /// 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); } } } } /// /// Re-define an existing and backup the originial color /// as it was before the application of the custom coloring. /// /// /// 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"); 是不是超级简单。 ### 4.代码编辑模块的编译与测试。 ![输入图片说明](https://gitee.com/akwkevin/aistudio.-wpf.-diagram/raw/master/Images/build.png) ![输入图片说明](https://gitee.com/akwkevin/aistudio.-wpf.-diagram/raw/master/Images/test.png) ### 5.WPF打印控制台数据 ``` 控制台打印方法支持切换运行输出方法Console.SetOut,核心代码如下: public class ConsoleWriter : TextWriter { private readonly Action _Write; private readonly Action _WriteLine; private readonly Action _WriteCallerInfo; public ConsoleWriter() { } /// /// Console 输出重定向 /// /// 日志方法委托(针对于 Write) /// 日志方法委托(针对于 WriteLine) public ConsoleWriter(Action write, Action writeLine, Action writeCallerInfo) { _Write = write; _WriteLine = writeLine?? write; _WriteCallerInfo = writeCallerInfo; } /// /// Console 输出重定向 /// /// 日志方法委托(针对于 Write) /// 日志方法委托(针对于 WriteLine) public ConsoleWriter(Action write, Action writeLine) { _Write = write; _WriteLine = writeLine; } /// /// Console 输出重定向 /// /// 日志方法委托 public ConsoleWriter(Action write) { _Write = write; _WriteLine = write; } /// /// Console 输出重定向(带调用方信息) /// /// 日志方法委托(后三个参数为 CallerFilePath、CallerMemberName、CallerLineNumber) public ConsoleWriter(Action write) { _WriteCallerInfo = write; } /// /// 使用 UTF-16 避免不必要的编码转换 /// public override Encoding Encoding => Encoding.Unicode; /// /// 最低限度需要重写的方法 /// /// 消息 public override void Write(string value) { if (_WriteCallerInfo != null) { WriteWithCallerInfo(value); return; } _Write(value); } /// /// 为提高效率直接处理一行的输出 /// /// 消息 public override void WriteLine(string value) { if (_WriteCallerInfo != null) { WriteWithCallerInfo(value); return; } _WriteLine(value); } /// /// 带调用方信息进行写消息 /// /// 消息 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); ### 6.最后介绍一下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#按照管脚取走数据即可。