按下'F' 进入AI正文
正文:
大家好!
在之前的帖子里,我们探讨了易语言(EPL)和 Python 之间进行交互的基础。今天,我非常激动地与大家分享一个终极版的混合编程示例。这个项目不仅展示了如何将 Python强大的 wxPython
GUI 库嵌入到易语言中,还实现了一个功能完整、界面现代的复杂应用程序。
这个项目最酷的地方在于:所有界面和交互逻辑都由 Python (wxPython) 实现,而核心的程序入口和事件决策权则保留在易语言中。 这意味着您可以利用 Python 社区庞大而成熟的生态库来打造华丽的界面,同时保留易语言快速开发和编写业务逻辑的优势。
核心特性一览
- 高级 AUI 框架: 使用
wx.lib.agw.aui
(Advanced User Interface) 实现类似专业开发工具(如 VS)的可停靠、可浮动、可拖拽的窗口布局管理器。
- 全面的控件展示: 几乎涵盖了
wxPython
中所有常用控件,包括:
- 基础控件: 按钮、文本框、单选/复选框、选择器等。
- 列表类控件: 可编辑的
ListView
、高性能的 DataViewListCtrl
。
- 高级控件: 网页视图
WebView
、富文本编辑器 RichTextCtrl
、支持关闭和拖拽的 AuiNotebook
选项卡。
- 表格与树:
Grid
电子表格和 TreeListCtrl
树状列表。
- 对话框: 各种标准对话框的调用演示。
- 动态布局与主题切换:
- 可以随时恢复默认界面布局。
- 支持一键切换多种界面主题 (如:VS2005, Modern, Chrome, Firefox 等),让你的应用瞬间焕然一新。
- 健壮的双向通信:
- 易语言 -> Python: 易语言负责初始化 Python 环境,加载模块,并调用指定的 Python 函数来创建和显示界面。
- Python -> 易语言: Python 中的所有 UI 事件(如按钮点击、菜单选择、窗口关闭)都会被捕获,并通过一个带缓冲区的回调函数安全地传递给易语言。易语言可以根据事件内容决定如何响应,甚至可以“否决”某些操作(例如,阻止窗口关闭)。
- 清晰的职责分离:
- 易语言端 (
_启动子程序
): 专注于环境设置、函数地址获取和资源清理。代码结构清晰,分为七个明确的步骤。
- Python 端 (
ui_library.py
): 专注于界面构建。将UI启动流程分解为 init_app
, show_main_frame
, run_main_loop
三个独立函数,使调用逻辑更清晰、更稳定。
实现原理解析
这个项目的核心是利用 Python 官方提供的 C API,通过易语言的 DLL 调用来实现的。
-
初始化与加载:
- 易语言首先通过
Py_SetPath
设置 Python 的搜索路径,确保能找到所有必要的模块。
Py_Initialize
启动 Python 解释器。
PyImport_ImportModule
动态导入我们的界面文件 ui_library.py
。
-
函数获取:
- 通过
PyObject_GetAttrString
从模块中获取我们需要调用的所有 Python 函数的地址,例如 init_app
、show_main_frame
等。
-
回调注册 (关键步骤):
- 易语言通过
&子程序地址
获取 HandleEventFromPython
这个回调子程序的内存地址。
- 使用
PyLong_FromVoidPtr
将这个地址转换为 Python 可以理解的整数对象。
- 调用 Python 的
set_event_handler
函数,将这个地址传递过去。
- Python 端使用
ctypes
将这个整数地址重新转换为一个可调用的 Windows 标准调用 (WINFUNCTYPE
) 函数指针。从此,E_LANG_CALLBACK
就成了易语言子程序在 Python 中的“代理”。
-
事件处理流程:
- 当用户在 wxPython 界面上点击一个按钮时,触发 Python 的事件处理函数。
- 该函数调用我们封装好的
handle_event
。
handle_event
创建一个缓冲区,然后调用 E_LANG_CALLBACK
,将事件名称(如 "button_click")和事件数据作为参数传递过去。
- 控制权回到易语言的
HandleEventFromPython
子程序。
- 易语言处理事件逻辑,并将需要返回给 Python 的结果字符串写入到 Python 提供的缓冲区内存中。
- Python 从缓冲区读取易语言的响应,并继续执行。
代码片段展示
易语言端:启动与回调核心逻辑
' --- 步骤 5: 将易语言回调函数地址传递给Python ---
pCallbackAddr = &HandleEventFromPython
pArg = PyLong_FromVoidPtr (pCallbackAddr)
' 调用 Python 的 set_event_handler 函数,把我们的回调函数地址传过去
pResult = PyObject_CallFunctionObjArgs_2p (pSetHandlerFunc, pArg, 0)
Py_DecRef (pArg)
.如果 (pResult = 0)
调试输出 (“回调注册失败!”)
' ... 清理并返回 ...
.如果结束
Py_DecRef (pResult)
' --- Python 回调实现 ---
.子程序 HandleEventFromPython, , , Python事件处理器
.参数 event_name, 整数型
.参数 event_data, 整数型
.参数 return_buffer, 整数型, , Python 提供的缓冲区地址
.参数 buffer_size, 整数型, , 缓冲区大小
.局部变量 e_event_name, 文本型
.局部变量 return_text, 文本型
.局部变量 return_bytes, 字节集
' 获取 Python GIL 锁,确保线程安全
gil_state = PyGILState_Ensure ()
e_event_name = 到文本 (指针到文本 (event_name))
调试输出 (“[易语言] 收到事件: '” + e_event_name + “'”)
' --- 在这里处理来自 Python 的所有事件 ---
.如果 (e_event_name = “window_close”)
.如果 (信息框 (“Python窗口请求关闭,是否允许?”, 1) = 1)
return_text = “0” ' 返回 "0" 表示允许关闭
.否则
return_text = “1” ' 返回 "1" 表示拒绝关闭
.如果结束
.否则
return_text = “易语言已收到事件: ” + e_event_name
.如果结束
' 将要返回的字符串,写入到 Python 提供的缓冲区
return_bytes = 到字节集 (return_text) + { 0 }
RtlMoveMemory (return_buffer, 取变量数据地址 (return_bytes), 取字节集长度 (return_bytes))
' 释放 GIL 锁
PyGILState_Release (gil_state)
Python 端:回调设置与事件触发
import ctypes
# 全局变量,用于保存易语言传递过来的回调函数指针
E_LANG_CALLBACK = None
def set_event_handler(callback_address: int):
"""由易语言调用,用于设置回调函数"""
global E_LANG_CALLBACK
if callback_address:
# 定义回调函数的原型,必须与易语言子程序的参数类型和顺序完全一致
callback_prototype = ctypes.WINFUNCTYPE(
None, # 返回值 (无)
ctypes.c_char_p, # event_name (字符串指针)
ctypes.py_object, # event_data (任意Python对象)
ctypes.c_char_p, # return_buffer (缓冲区地址)
ctypes.c_int # buffer_size (缓冲区大小)
)
# 将整数地址转换为可调用的函数
E_LANG_CALLBACK = callback_prototype(callback_address)
else:
E_LANG_CALLBACK = None
def handle_event(event_name: str, event_data):
"""在Python中触发一个事件,并通知易语言"""
if E_LANG_CALLBACK:
try:
# 创建一个足够大的缓冲区来接收易语言的返回值
buffer_size = 4096
return_buffer = ctypes.create_string_buffer(buffer_size)
# 将事件名编码为易语言默认的GBK格式
encoded_name = event_name.encode('gbk')
# 调用易语言的回调函数!
E_LANG_CALLBACK(encoded_name, event_data, return_buffer, buffer_size)
# 将易语言写入缓冲区的内容解码并返回
return return_buffer.value.decode('gbk')
except Exception as e:
return str(e)
return "E-lang callback not set."
# 在UI事件中调用 handle_event
class MainFrame(wx.Frame):
def __init__(self):
# ...
self.Bind(wx.EVT_CLOSE, self.on_close)
def on_close(self, event):
# 当窗口关闭时,调用handle_event通知易语言
result = handle_event("window_close", "")
if result == "0":
# 如果易语言返回 "0", 则销毁窗口
self.mgr.UnInit()
self.Destroy()
else:
# 否则,调用 event.Veto() 来阻止窗口关闭
self.log_message("易语言端取消了窗口关闭")
event.Veto()
如何使用?
- 确保你有一个可用的 Python 3.8 环境,并已安装
wxPython
。
- 将 Python 核心文件 (python38.dll, python38.zip, DLLs, Lib) 放在易语言程序生成的 .exe 所在的目录或其子目录中。
- 将
ui_library.py
文件也放在 .exe 旁边。
- 编译并运行易语言程序。
希望这个终极版的示例能为大家打开一扇新的大门,探索易语言与其他强大语言结合的无限可能!欢迎大家下载源码研究,一起交流讨论!