Skip to content

Instantly share code, notes, and snippets.

@engalar
Last active September 12, 2025 07:24
Show Gist options
  • Save engalar/b84b71693c4d1a8addd458e4eec53da3 to your computer and use it in GitHub Desktop.
Save engalar/b84b71693c4d1a8addd458e4eec53da3 to your computer and use it in GitHub Desktop.
studio pro plugin for tutorial
<!-- https://gist.github.com/engalar/b84b71693c4d1a8addd458e4eec53da3 -->
<!-- gh gist edit b84b71693c4d1a8addd458e4eec53da3 .\index.html -f index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Simple plugin demo</title>
<!-- 框架依赖: React, Tailwind, Babel, VConsole (引入本地资源,以便离线使用) -->
<script src="assets/react.development.js"></script>
<script src="assets/react-dom.development.js"></script>
<script src="assets/tailwindcss.js"></script>
<script src="assets/babel.min.js"></script>
<script src="assets/vconsole.min.js"></script>
<script>new VConsole();</script>
</head>
<body class="bg-gray-100 font-sans">
<div id="app"></div>
<script type="text/babel">
const { useState, useEffect } = React;
function App() {
const [inputValue, setInputValue] = useState('');
const [receivedMessages, setReceivedMessages] = useState([]);
// Function to send messages to the Mendix Studio Pro backend
const sendMessageToBackend = () => {
if (inputValue.trim() === '') {
return;
}
// Construct the message object to send
// The backend's onMessage function expects a 'Data' field which will be our message_object
// The C# host environment will wrap the second argument into e.Data
const messageObject = {
timestamp: new Date().toISOString(),
content: inputValue,
sender: 'frontend'
};
// Send the message to the C# host, which forwards it to the Python backend
window.parent.sendMessage("frontend:message", messageObject);
setInputValue(''); // Clear the input field
};
// Effect hook to set up and tear down the message event listener
useEffect(() => {
const handleBackendResponse = (event) => {
if (event.data && event.data.type === 'backendResponse') {//对应后端的PostMessage("backend:response", json.dumps(response))
try {
const payloadString = event.data.data;
// Parse the JSON string received from the backend
const payload = JSON.parse(payloadString);
setReceivedMessages((prevMessages) => [...prevMessages, payload]);
} catch (error) {
console.error("Error parsing backend response:", error);
// Optionally display an error message in the UI
}
}
};
// Add the event listener for messages from the host
window.addEventListener('message', handleBackendResponse);
// Cleanup function to remove the event listener when the component unmounts
return () => {
window.removeEventListener('message', handleBackendResponse);
};
}, []); // Empty dependency array ensures this runs once on mount and once on unmount
return (
<div className="p-4 max-w-2xl mx-auto bg-white shadow-lg rounded-lg mt-8">
<h1 className="text-3xl font-bold text-gray-800 mb-6">Mendix Plugin Demo</h1>
<div className="flex mb-6">
<input
type="text"
className="flex-grow p-3 border border-gray-300 rounded-l-lg focus:outline-none focus:ring-2 focus:ring-blue-500 text-gray-700"
value={inputValue}
onChange={(e) => setInputValue(e.target.value)}
onKeyPress={(e) => {
if (e.key === 'Enter') {
sendMessageToBackend();
}
}}
placeholder="Type a message to send to the backend..."
/>
<button
onClick={sendMessageToBackend}
className="px-6 py-3 bg-blue-600 text-white font-semibold rounded-r-lg hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 transition duration-200"
>
Send
</button>
</div>
<div>
<h2 className="text-2xl font-semibold text-gray-800 mb-3">Received from Backend:</h2>
{receivedMessages.length === 0 ? (
<p className="text-gray-600 italic">No messages received yet. Send something!</p>
) : (
<div className="bg-gray-50 p-4 rounded-lg border border-gray-200 h-64 overflow-y-auto">
{receivedMessages.map((msg, index) => (
<div key={index} className="mb-3 p-3 bg-white rounded-md shadow-sm last:mb-0">
<pre className="text-sm text-gray-800 whitespace-pre-wrap font-mono">
{JSON.stringify(msg, null, 2)}
</pre>
</div>
))}
</div>
)}
</div>
</div>
);
}
const root = ReactDOM.createRoot(document.getElementById('app'));
root.render(<App />);
</script>
</body>
</html>
# https://gist.github.com/engalar/b84b71693c4d1a8addd458e4eec53da3
# gh gist edit b84b71693c4d1a8addd458e4eec53da3 .\main.py -f main.py
# pip install pythonnet dependency-injector
from Mendix.StudioPro.ExtensionsAPI.Model.DomainModels import AssociationDirection, IDomainModel, IEntity, IAttribute, IAttributeType, IStoredValue, IAssociation, IIntegerAttributeType
from Mendix.StudioPro.ExtensionsAPI.Model.Pages import IPage
from Mendix.StudioPro.ExtensionsAPI.Model.Microflows import IMicroflow
from Mendix.StudioPro.ExtensionsAPI.Model.Projects import IProject, IModule, IFolder
import clr
from System.Text.Json import JsonSerializer
import json
from typing import Any, Dict, List, Protocol
# pythonnet库嵌入C#代码
clr.AddReference("System.Text.Json")
clr.AddReference("Mendix.StudioPro.ExtensionsAPI")
# 运行时环境提供的工具
PostMessage("backend:clear", '') # 清理IDE控制台日志
ShowDevTools() # 打开前端开发者工具
# 运行时环境提供的上下文变量
# currentApp:mendix model
# root:untyped model
# dockingWindowService
# region define
# fix c# json与python json转换问题
def serialize_json_object(json_object: Any) -> str:
# 将.NET对象序列化为JSON字符串
import System.Text.Json
return System.Text.Json.JsonSerializer.Serialize(json_object)
def deserialize_json_string(json_string: str) -> Any:
# 将JSON字符串反序列化为Python对象
return json.loads(json_string)
# mendix model事务工具
class TransactionManager:
"""with TransactionManager(currentApp, f"your transaction name"):"""
def __init__(self, app, transaction_name):
self.app = app # currentApp
self.name = transaction_name
self.transaction = None
def __enter__(self):
self.transaction = self.app.StartTransaction(self.name)
return self.transaction
def __exit__(self, exc_type, exc_val, exc_tb):
if exc_type is None:
self.transaction.Commit()
else:
self.transaction.Rollback()
self.transaction.Dispose()
return False # 允许异常继续传播
# endregion
# 获取所有模块
ms = currentApp.Root.GetModules()
# 获取名为Administration的模块
m = next((m for m in ms if m.Name == 'Administration'), None)
# 获取名为Administration.Account的实体
entity = next((e for e in m.DomainModel.GetEntities()
if e.Name == 'Account'), None)
# 编辑器打开模块Administration的DomainModel单元并选中Account实体
ret = dockingWindowService.TryOpenEditor(m.DomainModel, entity)
PostMessage("backend:info", f"{ret}")
def onMessage(e: Any):
"""
接收来自C#的消息
Args:
e (Any): The event object containing the message type and data from the frontend.
Returns:
None
"""
# 接收来自C#转发的前端消息,前端用window.parent.sendMessage("frontend:message", jsonMessageObj)发送消息
if e.Message == "frontend:message":
try:
# FIX: Use the .NET JsonSerializer to handle the incoming .NET JsonObject (e.Data)
# This correctly converts the .NET object to a standard JSON string.
request_string = JsonSerializer.Serialize(e.Data)
# Now, use Python's json library to parse the string into a Python dictionary.
request_object = json.loads(request_string)
if request_object:
# 处理逻辑
response = request_object # 简单的echo消息来模拟处理逻辑
# 发送消息给前端,前端可以用如下代码来接收
# window.addEventListener('message', (event) => {
# if (event.data && event.data.type === 'backendResponse') {
# const payload = event.data.data;// payload就是echo的response
# // your logic here
# }
# })
PostMessage("backend:response", json.dumps(response))
except Exception as ex:
PostMessage(
"backend:info", f"Fatal error in onMessage: {ex}\n{traceback.format_exc()}")
{
"name": "tutorial",
"author": "wengao",
"email": "[email protected]",
"ui": "index.html",
"plugin": "main.py"
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment